怎麼理解Condition?
在java.util.concurrent包中,有兩個很特殊的工具類,Condition和ReentrantLock,使用過的人都知道,ReentrantLock(重入鎖)是jdk的concurrent包提供的一種獨佔鎖的實現。它繼承自Dong Lea的 AbstractQueuedSynchronizer(同步器),確切的說是ReentrantLock的一個內部類繼承了AbstractQueuedSynchronizer,ReentrantLock只不過是代理了該類的一些方法,可能有人會問為什麼要使用內部類在包裝一層? 我想是安全的關係,因為AbstractQueuedSynchronizer中有很多方法,還實現了共享鎖,Condition(稍候再細說)等功能,如果直接使ReentrantLock繼承它,則很容易出現AbstractQueuedSynchronizer中的API被無用的情況。
言歸正傳,今天,我們討論下Condition工具類的實現。
ReentrantLock和Condition的使用方式通常是這樣的:
執行後,結果如下:
可以看到,
Condition的執行方式,是當線上程1中呼叫await方法後,執行緒1將釋放鎖,並且將自己沉睡,等待喚醒,
執行緒2獲取到鎖後,開始做事,完畢後,呼叫Condition的signal方法,喚醒執行緒1,執行緒1恢復執行。
以上說明Condition是一個多執行緒間協調通訊的工具類,使得某個,或者某些執行緒一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被帶呼叫)時 ,這些等待執行緒才會被喚醒,從而重新爭奪鎖。
那,它是怎麼實現的呢?
首先還是要明白,reentrantLock.newCondition() 返回的是Condition的一個實現,該類在AbstractQueuedSynchronizer中被實現,叫做newCondition()
它可以訪問AbstractQueuedSynchronizer中的方法和其餘內部類(AbstractQueuedSynchronizer是個抽象類,至於他怎麼能訪問,這裡有個很奇妙的點,後面我專門用demo說明 )
現在,我們一起來看下Condition類的實現,還是從上面的demo入手,
為了方便書寫,我將AbstractQueuedSynchronizer縮寫為AQS
當await被呼叫時,程式碼如下:
回到上面的demo,鎖被釋放後,執行緒1開始沉睡,這個時候執行緒因為執行緒1沉睡時,會喚醒AQS佇列中的頭結點,所所以執行緒2會開始競爭鎖,並獲取到,等待3秒後,執行緒2會呼叫signal方法,“發出”signal訊號,signal方法如下:
說明下,其實Condition內部維護了等待佇列的頭結點和尾節點,該佇列的作用是存放等待signal訊號的執行緒,該執行緒被封裝為Node節點後存放於此。
關鍵的就在於此,我們知道AQS自己維護的佇列是當前等待資源的佇列,AQS會在資源被釋放後,依次喚醒佇列中從前到後的所有節點,使他們對應的執行緒恢復執行。直到佇列為空。
而Condition自己也維護了一個佇列,該佇列的作用是維護一個等待signal訊號的佇列,兩個佇列的作用是不同,事實上,每個執行緒也僅僅會同時存在以上兩個佇列中的一個,流程是這樣的:
執行緒1呼叫reentrantLock.lock時,執行緒被加入到AQS的等待佇列中。
執行緒1呼叫await方法被呼叫時,該執行緒從AQS中移除,對應操作是鎖的釋放。
接著馬上被加入到Condition的等待佇列中,以為著該執行緒需要signal訊號。
執行緒2,因為執行緒1釋放鎖的關係,被喚醒,並判斷可以獲取鎖,於是執行緒2獲取鎖,並被加入到AQS的等待佇列中。
執行緒2呼叫signal方法,這個時候Condition的等待佇列中只有執行緒1一個節點,於是它被取出來,並被加入到AQS的等待佇列中。 注意,這個時候,執行緒1 並沒有被喚醒。
signal方法執行完畢,執行緒2呼叫reentrantLock.unLock()方法,釋放鎖。這個時候因為AQS中只有執行緒1,於是,AQS釋放鎖後按從頭到尾的順序喚醒執行緒時,執行緒1被喚醒,於是執行緒1回覆執行。
直到釋放所整個過程執行完畢。
可以看到,整個協作過程是靠結點在AQS的等待佇列和Condition的等待佇列中來回移動實現的,Condition作為一個條件類,很好的自己維護了一個等待訊號的佇列,並在適時的時候將結點加入到AQS的等待佇列中來實現的喚醒操作。
看到這裡,signal方法的程式碼應該不難理解了。
取出頭結點,然後doSignal
可以看到,正常情況 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)這個判斷是不會為true的,所以,不會在這個時候喚醒該執行緒。
只有到傳送signal訊號的執行緒呼叫reentrantLock.unlock()後因為它已經被加到AQS的等待佇列中,所以才會被喚醒。
總結:
本文從程式碼的角度說明了Condition的實現方式,其中,涉及到了AQS的很多操作,比如AQS的等待佇列實現獨佔鎖功能,不過,這不是本文討論的重點,等有機會再將AQS的實現單獨分享出來。
原文釋出時間為:2018-10-31
本文作者:Java技術驛站
本文來自雲棲社群合作伙伴“ofollow,noindex" target="_blank">Java技術驛站 ”,瞭解相關資訊可以關注“Java技術驛站 ”。