AbstractQueuedSynchronizer(AQS) 總結篇
簡介
在之前已經有6篇關於AQS原始碼分析的文章了,關於原始碼分析的一些問題可以去看看我之前的文章,文章連線可以在文末檢視。這一篇文章主要是對AQS的一些總結,或者說是面經。
AQS是什麼
AQS 全稱是AbstractQueuedSynchronizer,在java.util.concurrent.locks
包下面,是一個抽象的可以實現阻塞執行緒、排隊控制、喚醒執行緒等操作的同步器基礎框架類,AQS 可以實現排它鎖、共享鎖、條件鎖、計數器等相關功能。
父類AbstractOwnableSynchronizer
AQS 繼承的父類AbstractOwnableSynchronizer,該類僅一個屬性用於記錄當前持有鎖的執行緒,提供get/set方法。
變數:同步狀態state
state 欄位是一個非常重要的欄位,可以基於state欄位的值定義出不同的同步鎖功能,比如:
- 基於state 的值實現排他鎖
state 值為1代表鎖被佔用,值為0時代表鎖未被佔用。
代表類:ReentrantLock - 基於state的值實現讀寫鎖
state 被分成兩部分,高16位記錄讀鎖次數,低16位記錄寫鎖次數
代表類:ReentrantReadWriteLock - 基於state的值實現限制執行緒數
初始化一個state值,表示最大限制數,即可以做到允許最多N個執行緒同時執行,達到限流效果
代表類:Semaphore - 基於state的值實現倒計數
初始化一個state值,state值為0時觸發喚醒動作
代表類:CountDownLatch
兩個佇列
AQS 裡面有兩個佇列,我稱為同步佇列和條件佇列。條件佇列主要是實現條件鎖時用到的佇列,同步佇列就是維護喚醒執行緒的佇列。
- 同步佇列
主要用於維護獲取互斥鎖失敗時入隊的執行緒 - 條件佇列
呼叫await()
的時候會釋放鎖,然後執行緒會加入到條件佇列,呼叫signal()
喚醒的時候會把條件佇列中的執行緒節點移動到同步佇列中,等待再次獲得鎖
可以重寫的API
AQS 提供了 5 個可以自定義實現功能的API方法,基於這些方法,則可以實現不同型別的鎖功能。
- protected boolean tryAcquire(int arg)
嘗試一次獲得一個排它鎖 - protected boolean tryRelease(int arg)
嘗試一次釋放一個排它鎖 - protected int tryAcquireShared(int arg)
嘗試一次獲得一個共享鎖 - protected boolean tryReleaseShared(int arg)
嘗試一次釋放一個共享鎖 - protected boolean isHeldExclusively()
驗證排它鎖是否被佔用
提供的模版方法
下面的這些模版方法,都用到了上面可以重寫的API方法。
-
基於
tryAcquire
API提供的模版方法-
獲得一個排它鎖,直到成功獲得鎖
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
-
獲得一個排它鎖,可被中斷
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
-
獲得一個排它鎖,可超時或中斷
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
其中
tryAcquire(arg)
方法是需要自己實現的方法 -
-
基於tryAcquireShared API提供的模版方法
-
獲得一個共享鎖,直到成功獲得鎖
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
其中
tryAcquireShared(arg)
方法是需要自己實現的方法 -
獲得一個共享鎖,可被中斷
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
-
獲得一個共享鎖,支援超時或中斷
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); }
-
-
釋放一個排它鎖
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
其中
tryRelease(arg)
方法是需要自己實現的方法 -
釋放一個共享鎖
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
其中
tryReleaseShared(arg)
方法是需要自己實現的方法
節點狀態
AQS 定義了5個佇列中節點狀態:
- 值為0,初始化狀態,表示當前節點在sync佇列中,等待著獲取鎖。
- CANCELLED,值為1,表示當前的執行緒被取消;
- SIGNAL,值為-1,表示當前節點的後繼節點包含的執行緒需要執行,也就是unpark;
- CONDITION,值為-2,表示當前節點在等待condition,也就是在condition佇列中;
- PROPAGATE,值為-3,表示當前場景下後續的acquireShared能夠得以執行;
其他
還有一個與AQS非常相似的類——AbstractQueuedLongSynchronizer,從命名上來看,多了一個Long,從原始碼上來看,他們兩個有完全相同的結構、屬性和方法,唯一不同之處就在於所有與狀態相關的引數和結果都定於為long型別,而不是int型別,當需要建立64位狀態的同步器(例如多級鎖和屏障)時,AbstractQueuedLongSynchronizer類可能很有用。