@[toc]
1.學習AQS的思路
- 瞭解原理,提高思路
- 先了解如何使用,應用場景,再去分析它的結構
2.為什麼需要AQS
- 鎖的協作類共同點:閘門 (和 ReentrantLock、Semaphore相似)
- 不僅 ReentrantLock、Semaphore、像CountDownLatch、都有這樣的協作 (或者同步)功能,其實,他們底層都用了一個共同的基類,這就是 AQS
- 因為這些協作類有很多工作都是類似的,所以如果提取出一個工具類,那麼就直接就可以用
- Semaphore 、CountDownLatch 內部有一個類 Sync , Sync類繼承了AQS
3.AQS的作用
如果沒有AQS:
: 就需要每個協作工具直接實現
: 線程的阻塞與解除阻塞
: 隊列的管理
- AQS是一個用於構建鎖、同步器、協作工具類的工具類(框架)。有了AQS以後,更多的協作工具類都可以很方便得被寫出來
- 有了AQS,構建線程協作類就容易多了
4.AQS的重要性、地位
AbstractQueuedSynchronizer 是 Doug Lea 寫的,從JDK1.5加入的一個基於FIFO等待隊列實現的一個用於實現同步器的基礎框架,我沒用IDE看AQS的實現類,可以發現實現類有這些實現
5.AQS內部原理解析
AQS三大核心:
- state
- 控制線程搶鎖和配合的FIFO隊列
- 期望協作工具類去實現的獲取/釋放等重要方法
State狀態
- 這裡等state的具體含義,會根據具體實現類的不同而不同,比如在Semaphore 裡,它表示 “ 剩餘的許可證數量” ,而CountDawnLatch 裡,它表示 “ 還需要倒數的數量 ”
- state 是 volatile修飾的,會被併發地修改,所以所有修改state的方法要保證線程安全,比如getState 、setState 以及 compareAandState 操作來讀取和更新這個狀態。這些方法都依賴與j.u.c.atomic包的支持
/**
* The synchronization state.
* 線程同步狀態 利用來volatile 保證可見性
*/
private volatile int state;
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* 利用了 unsafe 類的 cas操作
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
像在 ReentrantLock中
- state用來表示 “鎖”的佔有情況,包括可重入鎖計數
- 當state 的值為 0的時候,標識改Lock不被任何線程所佔有
FIFO隊列
這個隊列用來存放 “等待的線程”,AQS就是 “排對管理器”,當多個線程爭用同一把鎖的時候,必須有排隊機制講那些沒能拿到鎖的線程串在一起。當鎖釋放時,鎖管理器就會挑選一個合適的線程佔有這個剛剛釋放的鎖
獲取/釋放的方法
這裡的獲取和釋放方法,是利用AQS的協作工具類裡最重要的方法,是由協作類自己實現的,並且含義各不相同
獲取:
- 獲取操作會依賴state變量,經常會阻塞(比如獲取不到鎖的時候)
- 在Semaphore 中 ,獲取就是acquire 方法,作用就是獲取一個許可證
- 而在CountDownLatch裡,獲取就是await方法,作用等待,直到倒數結束
釋放:
- 釋放操作不會被阻塞
- 在Semaphore中 ,釋放就是 release 方法,作用是釋放一個許可證
- CountDownLatch 裡面,獲取就是countDown 方法,作用是 “倒數一個數”
6.應用實例,源碼解析
我們看下 CountDownLatch的源碼
- 構造方法
/**
* 構造函數 , 傳入int值,判斷小於0 給Sync 賦值
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Sync 繼承類AQS Sync的構造獲
*/
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
/**
*把AQS state值設置 newState
*/
protected final void setState(int newState) {
state = newState;
}
/**
* 調用如下 獲取AQS類的
* private volatile int state;
*/
protected final int getState() {
return state;
}
- getCount() 方法
// 調用AQS 的getState()方法
int getCount() {
return getState();
}
//AQS返回state的值
protected final int getState() {
return state;
}
- await() 方法
//進行等待 調用AQS sync.acquireSharedInterruptibly(1);
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果小於0 則放入等待隊列中
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//包裝成Node節點
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//死循環
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
//中斷線程的方法
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//park 調用的 LockSupport.park(this);
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- countDown() 方法
// 減一減一 直到0開始喚醒
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//判斷是否等於0
if (tryReleaseShared(arg)) {
// 喚醒所有線程
doReleaseShared();
return true;
}
return false;
}
- 總結
調用CountDownLatch的await方法時,便會嘗試獲取 "共享鎖",不過一開始是獲取不到鎖的,於是線程阻塞/
而 “共享鎖” 可獲取到的條件,就是 “鎖計數器” 的值為0。
而 “鎖計數器初始值為count” 沒當一個線程調用 才減一
當count 線程調用countDown()之後 , “鎖計數器” 才為 0,而前面提到的等待獲取共享個i昂鎖的線程才能繼續運行
7.AQS優質學習資源
- 美團技術團隊《從ReentrantLock的實現看AQS的原理及應用》: https://mp.weixin.qq.com/s/sA01gxC4EbgypCsQt5pVog.
- 老錢《打通 Java 任督二脈 —— 併發數據結構的基石》:https://juejin.im/post/5c11d6376fb9a049e82b6253.
- HongJie《一行一行源碼分析清楚AbstractQueuedSynchronizer》:https://javadoop.com/post/AbstractQueuedSynchronizer.
- 愛吃魚的KK《AbstractQueuedSynchronizer 源碼分析 (基於Java 8)》:https://www.jianshu.com/p/e7659436538b.
- waterystone《Java併發之AQS詳解》:https://www.cnblogs.com/waterystone/p/4920797.html.
- 英文論文的中文翻譯:https://www.cnblogs.com/dennyzhangdd/p/7218510.html.
- AQS作者的英文論文:http://gee.cs.oswego.edu/dl/papers/aqs.pdf.