1. 程式人生 > >Java多執行緒——AQS框架原始碼閱讀

Java多執行緒——AQS框架原始碼閱讀

AQS,全稱AbstractQueuedSynchronizer,是Concurrent包鎖的核心,沒有AQS就沒有Java的Concurrent包。它到底是個什麼,我們來看看原始碼的第一段註解是怎麼說明



看完第一段,總結下

  • AQS是一個同步的基礎框架,基於一個先進先出的佇列。
  • 鎖機制基於一個狀態值,它是原子值。
  • AQS的子類負責定義與操作這個狀態值,但必須通過AQS提供的原子操作
  • AQS剩餘的方法就是圍繞佇列,與執行緒阻塞喚醒等功能

基於以上概念,我們看看原始碼到底是這麼實現這些功能的

AQS的成員變數

state

private volatile int state;
該變數標記為volatile

,說明該變數是對所有執行緒可見的。作用在於每個執行緒改變該值,都會馬上讓其他執行緒可見,在CAS(可見鎖概念與鎖優化)的時候是必不可少的。在AQS類中,不會直接操作這個值,而是交由它的子類去操作和定義他的作用。

Node、head、tail

AQS中有一個靜態內部類Node,其實現是一個雙向連結串列。headtail則是這個連結串列的頭尾指標。作用是儲存獲取鎖失敗的阻塞執行緒。同樣的,這個連結串列是會被多個執行緒操作的,所以它裡面的變數多是被標記為volatile,並且操作也要通過CAS等原子方法去執行。
Node還有一個模式的屬性:獨佔模式共享模式。獨佔模式下,鎖是執行緒獨佔的,而共享模式下,鎖是可以被多個執行緒佔用的。

VarHandler

對於大多數需要操作的原子屬性,都對應會有一個大寫的值,它的類是VarHandler。例如state、head、tail都有對應的VarHandler,STATE、HEAD、TAIL。VarHandler是1.9的新特性,提供了類似於原子操作以及Unsafe操作的功能,裡面的原子操作大多是native方法,比較難檢視原始碼。

ConditionObject

條件佇列,是AQS中一個非常關鍵內部類。這個名字起非常奇異,讓人搞不懂,看它類註釋也看不懂說了什麼。看看AQS頭部註解

這個類是為了讓子類支援獨佔模式的。深入看其中的原始碼實現,其實就是Node在功能性上的封裝,最終讓子類實現讓當前執行緒怎麼獨佔一個Object鎖。await()、dosign()

等方法就是讓執行緒阻塞、加入佇列、喚醒執行緒等。AQS框架下基本各種獨佔的加鎖,解鎖等操作到最後都是基於這個類實現的。該類是提供給子類去使用的,具體實現等下次說ReentranLock再深入瞭解。有人可能覺得為什麼實現這個內部類,又不用,而是給子類去用,那為什麼不放到子類去呢?其實答案,很簡單,抽象加模板模式

p.s. 只有獨佔鎖才能配合該類使用。

AQS的成員函式

AQS的公用的方法,主要是加鎖與解鎖方法。以下方法只提供了模板,部分實現還是在子類當中,直接呼叫會丟擲異常。

acquire()

嘗試獲取鎖,失敗則進入佇列。

先執行tryAcquire()(子類實現),成功則直接返回,如果是獲取鎖失敗,則執行addWaiter(),通過CAS在雙向連結串列的尾部新增一個新獨佔節點。

然後把節點丟到acquireQueued()中執行。該方法其實就是自旋嘗試獲取鎖或阻塞執行緒(子類實現決定)。一開始,獲取新節點的前驅節點,如果這個節點是head,則證明只有兩個節點,此時再次執行tryAcquire()嘗試獲取鎖,若獲取成功,則不需要中斷,成功結束。

如果還是獲取失敗,則執行shouldParkAfterFailedAcquire(),根據前驅節點狀態(子類設值)判斷是否繼續自旋(當waitStatus為初始值,重複上一步,直到前面的節點一直在減少到前驅節點為head)或者阻塞執行緒(當waitStatus標記為SIGNAL)

最後如果acquireQueued()返回需要阻塞,則執行selfInterrupt()設定執行緒為中斷

可以看回acquire()函式的寫法,十分的藝術。利用條件判斷的短路規則,實現在if()條件內巢狀判斷執行語音。一般人(筆者本人)如果要實現這個功能,會這麼寫

所以下次遇到類似巢狀if條件判斷的語句,可以學習下acquire()的這種短路寫法。贊