1. 程式人生 > >Java多線程——AQS框架源碼閱讀

Java多線程——AQS框架源碼閱讀

ould 入隊 image fontsize nts 所有 obj 尾指針 ava

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()的這種短路寫法。贊

Java多線程——AQS框架源碼閱讀