AQS(AbstractQueuedSynchronizer)是一個用於構建鎖和同步器的框架,許多同步器都可以通過AQS很容易並且高效的構造出來。不僅Reentrant和Semaphore是基於AQS構建的,還包括CountDownLatch、ReentrantReadWriteLock、SynchronousQueue和FutureTask。
高層抽象
在基於AQS構建的同步器類中,最基本的操作包括各種形式的獲取操作和釋放操作。
獲取操作是一種依賴狀態的操作,並且通常會阻塞直到達到了理想狀態。如:
- 使用ReentrantLock時,“獲取”操作意味著“等待直到鎖可被獲取”。
- 使用Semaphore時,“獲取”操作意味著“等待直到許可可被獲取”。
- 使用CountDownLatch時,“獲取”操作意味著“等待直到閉鎖達到結束狀態”。
- 使用FutureTask時,“獲取”操作意味著“等待直到任務完成”。
釋放並不是一個可阻塞的操作,當執行“釋放”操作時,所有在請求時被阻塞的執行緒都會開始執行。
內部原理
狀態機
AQS提供了一個高效的狀態機模型,用來管理同步器類中的狀態和。
狀態
AQS使用一個整數state以表示狀態,並通過getState、setState及compareAndSetState等protected型別方法進行狀態轉換。巧妙的使用state,可以表示任何狀態,如:
- ReentrantLock用state表示所有者執行緒已經重複獲取該鎖的次數。
- Semaphore用state表示剩餘的許可數量。
- CountDownLatch用state表示閉鎖的狀態,如關閉、開啟。
- FutureTask用state表示任務的狀態,如尚未開始、正在執行、已完成、已取消。
除了state,在同步器類中還可以自行管理一些額外的狀態變數。如:
- ReentrantLock儲存了鎖的當前所有者的資訊,這樣就能區分某個獲取操作是重入的還是競爭的。
- FutureTask用result表示任務的結果,該結果可能是計算得到的答案,也可能是丟擲的異常。
狀態轉換
狀態轉換則表現為不同的獲取操作和釋放操作,其標準形式如下:
boolean acquire () throws InterruptedException {
while (當前狀態不允許獲取操作) {
if (需要阻塞獲取請求) {
如果當前執行緒不在佇列中,則將其插入佇列
阻塞當前新城
}
else
返回失敗
}
可能更新同步器的狀態
如果當前執行緒在佇列中,則將其移出佇列
返回成功
}
void release () {
更新同步器的狀態
if (新的狀態允許某個被阻塞的執行緒獲取成功)
接觸佇列中一個或多個執行緒的阻塞狀態
}
複製程式碼
為什麼10行只是“可能”,而不是“必然”更新同步器的狀態呢?因為獲取同步器的某個執行緒可能對其他執行緒能否也獲取該同步器造成影響,也可能不影響。如使用獨佔的ReentrantLock時,一個執行緒獲取鎖後,其他執行緒就不能再獲取鎖,於是需要更新同步器的狀態;但使用CountDownLatch時,一個執行緒獲取閉鎖時(包括正在獲取和獲取後),不會影響其他執行緒能否獲取它,因此不需要更新同步器的狀態。
一些約定
根據是否支援阻塞、是否支援獨佔等,獲取操作和釋放操作都有多個實現。獲取操作有acquire、acquireShared、tryAcquire、tryAcquireShared等,釋放操作有release、releaseShared、tryRelease、tryReleaseShared等。
還有acquireNanos、acquireInterruptibly等實現,為了講解方便,暫時忽略它們。
不帶try字首的方法是阻塞的(當然release、releaseShared不是可阻塞的),通過呼叫帶try字首的相應版本實現,如acquire內部呼叫tryAcquire並維護相關邏輯。AQS抽象類中提供了不帶try字首的方法,並以final修飾,在實現同步器時應直接使用;需要覆寫的是帶try字首的方法。對於這些方法,約定通過返回值告知呼叫者(一般是AQS)獲取或釋放操作是否成功,一些特殊的值代表額外的資訊:
- 對於tryAcquire,如果返回true,則表示獲取成功;否則返回false。
- 對於tryAcquireShared,如果返回一個負值,那麼表示獲取操作失敗,返回零值表示同步器通過獨佔方式被獲取,返回正值表示同步器通過非獨佔方式被獲取。
- 對於tryRelease與tryReleaseShared方法來說,如果返回true,則表示已完全釋放,所有在獲取同步器時被阻塞的執行緒都可以被恢復執行;否則返回false。
要想基於AQS構建同步器,就必須對上述四個方法爛熟於心。
總結
- state表示狀態,其他狀態需要自行維護
- 直接使用不帶try字首的方法,並覆寫帶try字首的方法
- 對於帶try字首的方法,約定通過返回值告知呼叫者(一般是AQS)獲取或釋放操作的結果
本文雖短,且非常重要,是後文分析ReentrantLock等的基礎。同時,又是一個高效併發的經典設計案例。
本文連結:AQS的基本原理
作者:猴子007
出處:monkeysayhi.github.io
本文基於 知識共享署名-相同方式共享 4.0 國際許可協議釋出,歡迎轉載,演繹或用於商業目的,但是必須保留本文的署名及連結。