Java多執行緒——AQS框架原始碼閱讀
AQS,全稱AbstractQueuedSynchronizer
,是Concurrent包鎖的核心,沒有AQS就沒有Java的Concurrent包。它到底是個什麼,我們來看看原始碼的第一段註解是怎麼說明
看完第一段,總結下
- AQS是一個同步的基礎框架,基於一個先進先出的佇列。
- 鎖機制基於一個狀態值,它是原子值。
- AQS的子類負責定義與操作這個狀態值,但必須通過AQS提供的原子操作
- AQS剩餘的方法就是圍繞佇列,與執行緒阻塞喚醒等功能
基於以上概念,我們看看原始碼到底是這麼實現這些功能的
AQS的成員變數
state
private volatile int state;
該變數標記為volatile
Node、head、tail
AQS中有一個靜態內部類Node
,其實現是一個雙向連結串列。head
與tail
則是這個連結串列的頭尾指標。作用是儲存獲取鎖失敗的阻塞執行緒。同樣的,這個連結串列是會被多個執行緒操作的,所以它裡面的變數多是被標記為volatile
,並且操作也要通過CAS等原子方法去執行。
Node還有一個模式的屬性:獨佔模式和共享模式。獨佔模式下,鎖是執行緒獨佔的,而共享模式下,鎖是可以被多個執行緒佔用的。
VarHandler
對於大多數需要操作的原子屬性,都對應會有一個大寫的值,它的類是VarHandler
。例如state、head、tail
都有對應的VarHandler,STATE、HEAD、TAIL
。VarHandler是1.9
的新特性,提供了類似於原子操作以及Unsafe操作的功能,裡面的原子操作大多是native方法,比較難檢視原始碼。
ConditionObject
條件佇列,是AQS中一個非常關鍵內部類。這個名字起非常奇異,讓人搞不懂,看它類註釋也看不懂說了什麼。看看AQS頭部註解
這個類是為了讓子類支援獨佔模式的。深入看其中的原始碼實現,其實就是Node在功能性上的封裝,最終讓子類實現讓當前執行緒怎麼獨佔一個Object鎖。await()、dosign()
ReentranLock
再深入瞭解。有人可能覺得為什麼實現這個內部類,又不用,而是給子類去用,那為什麼不放到子類去呢?其實答案,很簡單,抽象加模板模式。p.s. 只有獨佔鎖才能配合該類使用。
AQS的成員函式
AQS的公用的方法,主要是加鎖與解鎖方法。以下方法只提供了模板,部分實現還是在子類當中,直接呼叫會丟擲異常。
acquire()
嘗試獲取鎖,失敗則進入佇列。
先執行tryAcquire()
(子類實現),成功則直接返回,如果是獲取鎖失敗,則執行addWaiter()
,通過CAS在雙向連結串列的尾部新增一個新獨佔節點。
然後把節點丟到acquireQueued()
中執行。該方法其實就是自旋嘗試獲取鎖或阻塞執行緒(子類實現決定)。一開始,獲取新節點的前驅節點,如果這個節點是head,則證明只有兩個節點,此時再次執行tryAcquire()
嘗試獲取鎖,若獲取成功,則不需要中斷,成功結束。
如果還是獲取失敗,則執行shouldParkAfterFailedAcquire()
,根據前驅節點狀態(子類設值)判斷是否繼續自旋(當waitStatus為初始值,重複上一步,直到前面的節點一直在減少到前驅節點為head)或者阻塞執行緒(當waitStatus標記為SIGNAL)
最後如果acquireQueued()
返回需要阻塞,則執行selfInterrupt()
設定執行緒為中斷
可以看回acquire()
函式的寫法,十分的藝術。利用條件判斷的短路規則,實現在if()
條件內巢狀判斷執行語音。一般人(筆者本人)如果要實現這個功能,會這麼寫
所以下次遇到類似巢狀if條件判斷的語句,可以學習下acquire()
的這種短路寫法。贊