本文首發於一世流雲的專欄: https://segmentfault.com/blog...
本章將繼續以ReentrantLock的調用爲例,說明AbstractQueuedSynchronizer提供的Conditon等待功能。關於Conditon接口的介紹,能夠參見:Java多線程進階(二)—— juc-locks鎖框架:接口。segmentfault
J.U.C包提供了Conditon接口,用以對原生的Object.wait()
、Object.notify()
進行加強。多線程
Condition接口的實現類實際上是在AQS中——ConditionObject
,ReentranLock的newConditon方法實際上是建立了一個AbstractQueuedSynchronizer.ConditionObject
對象:框架
Condition做爲AQS的內部類,複用了AQS的結點,維護一個條件隊列,隊列初始時的結構以下:ui
假設如今有3個線程:ThreadA、ThreadB、ThreadC,一個Conditon實現對象。
ReentrantLock lock = new ReentrantLock();
Conditon con = lock.newConditon();
線程將以如下的時序調用:spa
//ThreadA先調用lock方法獲取到鎖,而後調用con.await() //ThreadB獲取鎖,調用con.signal()喚醒ThreadA //ThreadB釋放鎖
上述方法,先對線程中斷作一次預判斷,而後將線程包裝成結點插入【條件隊列】,插入完成後,條件隊列的結構以下:線程
咱們知道,await()
方法會釋放當前線程持有的鎖,這個過程其實就是fullyRelease方法的做用:3d
而後,判斷當前結點是否是在【等待隊列】中,不在的話就會阻塞線程。
最終線程A釋放了鎖,並進入阻塞狀態。調試
因爲Condition的signal方法要求線程必須得到與此Condition對象相關聯的鎖,因此這裏有箇中斷判斷:code
而後,會調用doSignal方法,刪除條件隊列中的隊首CONDITION類型結點:對象
刪除完成後,transferForSignal方法會將CONDITON結點轉換爲初始結點,並插入【等待隊列】:
此時,【條件隊列】已經空了:
而ThreadA被包裝成新結點後,插入【等待隊列】:
終於ThreadB釋放了鎖,釋放成功後,會調用unparkSuccessor方法(參加AQS獨佔功能的講解),喚醒隊列中的首結點:
最終等待隊列結構以下:
ThreadA被喚醒後,從await方法的阻塞處開始繼續往下執行:
以後會調用acquireQueued方法再次嘗試獲取鎖,獲取成功後,最終等待隊列狀態以下:
本章以ReentrantLock的公平鎖爲例,分析了AbstractQueuedSynchronizer的Condition功能。
經過分析,能夠看到,當線程在指定Condition對象上等待的時候,其實就是將線程包裝成結點,加入了條件隊列,而後阻塞。當線程被通知喚醒時,則是將條件隊列中的結點轉換成等待隊列中的結點,以後的處理就和獨佔功能徹底同樣。
除此以外,Condition還支持限時等待、非中斷等待等功能,分析思路是同樣的,讀者能夠本身去閱讀AQS的源碼,經過使用示例,加入調試斷點一步步看內部的調用流程,主幹理順了以後,再看其它分支,實際上是殊途同歸的。