AbstractQueuedSynchronizer(AQS),即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),它是JUC併發包中的核心基礎組件。AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態。
Node節點
AQS維護了一個靜態內部類Node,它包含了以下屬性:
- static final Node SHARED:一種標識,表示被標識的結點是共享式。
- static final Node EXCLUSIVE:一種標識,表示被標識的結點是獨佔式。
- volatile int waitStatus:當前結點的等待狀態,它有1,0,-1,-2,-3五個取值。
- static final int CANCELLED = 1; 被中斷或者獲取同步狀態超時會被置爲該狀態,且在該狀態下的線程不再被阻塞。
- 0爲初始化默認值。
- static final int SIGNAL = -1; 線程如果釋放了或者取消了同步狀態,則會將對應的結點置爲該狀態,用於通知下一個節點,準備獲取同步狀態。
- static final int CONDITION = -2; 當前節點在Condition中的等待隊列上,其他線程調用了singal方法後,該節點會從等待隊列轉移到AQS的同步隊列上,等待獲取同步鎖。
- static final int PROPAGATE = -3; 與共享式獲取同步狀態有關,該狀態標識的結點對應線程處於可運行的狀態。
- volatile Node prev:前驅結點(給CLH同步隊列使用)
- volatile Node next:後繼結點(給CLH同步隊列使用)
- volatile Thread thread:當前節點對應的線程
- Node nextWaiter:當前節點在Condition中等待隊列上的下一個節點(給Condition等待隊列使用)
上述屬性中,注意其中a、b以及c中的狀態信息都是static類型的,也就是說它們只是一些標識信息,是所有節點共用的。而其他屬性則是每個節點自身的信息。
CLH同步隊列
CLH(Craig, Landin, and Hagersten locks) 同步隊列 是一個FIFO雙向隊列,其內部通過節點head和tail記錄隊首和隊尾元素,隊列元素的類型爲Node。AQS依賴它來完成同步狀態state的管理,CLH提供了一些重要的函數:
線程同步狀態相關
- tryAcquire(int arg):獨佔式獲取同步狀態,獲取同步狀態成功後,其他線程需要等待該線程釋放同步狀態才能獲取同步狀態
- tryRelease(int arg):獨佔式釋放同步狀態;
- tryAcquireShared(int arg):共享式獲取同步狀態,返回值大於等於0則表示獲取成功,否則獲取失敗;
- tryReleaseShared(int arg):共享式釋放同步狀態;
- acquire(int arg):獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用可重寫的tryAcquire(int arg)方法;
- acquireInterruptibly(int arg):與acquire(int arg)相同,但是該方法響應中斷,當前線程爲獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常並返回;
- tryAcquireNanos(int arg,long nanos):超時獲取同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那麼將會返回false,已經獲取則返回true;
- acquireShared(int arg):共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式的主要區別是在同一時刻可以有多個線程獲取到同步狀態;
- acquireSharedInterruptibly(int arg):共享式獲取同步狀態,響應中斷;
- tryAcquireSharedNanos(int arg, long nanosTimeout):共享式獲取同步狀態,增加超時限制;
- release(int arg):獨佔式釋放同步狀態,該方法會在釋放同步狀態之後,將同步隊列中第一個節點包含的線程喚醒;
- releaseShared(int arg):共享式釋放同步狀態;
CLH狀態相關
- getState():返回同步狀態的當前值;
- setState(int newState):設置當前同步狀態;
- compareAndSetState(int expect, int update):使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性;
- CLH隊列入列就是tail指向新節點、新節點的prev指向當前最後的節點,當前最後一個節點的next指向當前節點。
同步狀態的獲取與釋放
在AQS中維護着一個FIFO(先進先出)的同步隊列CLH,當線程獲取同步狀態失敗後,則會加入到這個CLH同步隊列的對尾並一直保持着自旋。在CLH同步隊列中的線程在自旋時會判斷其前驅節點是否爲首節點,如果爲首節點則不斷嘗試獲取同步狀態,獲取成功則退出CLH同步隊列。當線程執行完邏輯後,會釋放同步狀態,釋放後會喚醒其後繼節點。
獲取同步狀態(入口:acquire相關方法)
釋放同步狀態(入口:release相關方法)
Condition等待隊列
在經典的生產者-消費者模式中,可以使用Object.wait()和Object.notify()阻塞和喚醒線程,但是只能有一個等待隊列。在AQS中也提供了類似的機制,但是可以更靈活的建立多個等待隊列。Condition隊列有以下特點:
- 提供了await()方法使一個線程陷入等待,並提供signal()方法來支持線程的喚醒。
- Condition和Wait/Notify通知機制類似,必須在Lock中使用,即在使用之前必須是已經獲取到鎖的狀態(Wait/Notify必須在synchronized中使用)。
- 執行signal()方法後,等待隊列的結點被轉移至CLH同步隊列中競爭鎖。
Condition等待隊列也使用了和CLH同步隊列一樣的結點,類似地使用firstWaiter與lastWaiter兩個指針指向隊列首尾,但是隻使用了node的nextWaiter屬性(而沒有用next和prev)使其變成了一個單向鏈表(也是FIFO)。Condition等待隊列提供了一些控制線程狀態的重要函數:
- 線程等待阻塞
- await():讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒或者中斷此線程。
- await(long time, TimeUnit unit):讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒、中斷、超時。
- awaitNanos(long nanosTimeout):納秒級別控制,讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒、中斷、超時,並返回剩餘時間。
- awaitUninterruptibly():讓當前線程進入等待阻塞狀態,且不會響應線程的中斷。
- awaitUntil(Date deadline):類似於第二種情況。
- 線程喚醒
- signal():喚醒一個等待在Condition上的線程,使其轉到同步隊列中
- signalAll():喚醒所有等待在Condition上的線程,使它們轉到同步隊列中
LockSupport
可以發現,在同步隊列和等待隊列中對線程的很多操作都與LockSupport有關。LockSupport對外提供了一些靜態方法用來操控當前線程:
- 線程阻塞相關
- park():阻塞當前線程,直到其他線程調用unpark(Thread)方法或者當前線程被中斷
- parkNanos(long):阻塞當前線程,直到其他線程調用unpark(Thread)方法、當前線程被中斷、超時(相對時間)
- parkUntil(long):阻塞當前線程,直到某個時間(絕對時間)
- 線程喚醒相關
- unpark(Thread):喚醒某個線程
而LockSupport的底層還是由Unsafe類來完成。