點1:AQS即AbstractQueuedSynchronizer贴谎,內(nèi)部維護了一個CLH隊列叛溢,在JDK的lock實現(xiàn)中深受重用膏潮,詳細介紹請看,[http://www.importnew.com/22102.html]
點2:Condition,JDK API中是這樣解釋的:Condition 將 Object 監(jiān)視器方法(wait掌腰、notify 和 notifyAll)分解成截然不同的對象狰住,以便通過將這些對象與任意 Lock 實現(xiàn)組合使用,為每個對象提供多個等待 set(wait-set)齿梁。其中催植,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監(jiān)視器方法的使用。
看過ReentrantLock源碼的朋友對這兩位應該不陌生查邢,今天就和大家一起學習一下這兩個兄弟之間的CP曖昧。
Condition的使用總是伴隨著Lock的使用酵幕,先跑一個例子給大家:
執(zhí)行結果如下
一扰藕、先來分析一下 await()方法
(1)addConditionWaiter()將當前線程包裝為node維護在 condition自己的隊列中
(2)fullyRelease(node)將當前線程已經(jīng)獲取到的 lock釋放
(3)while 遍歷AQS的隊列,看當前節(jié)點是否在隊列(注意此時的隊列已經(jīng)是AQS的隊列)中芳撒,不在 說明它還沒有競爭鎖的資格邓深,所以繼續(xù)將自己沉睡。直到它被加入到隊列(AQS隊列)中笔刹,那么什么時候被加入隊列嗎芥备?這里先賣個關子。
(4)被喚醒后舌菜,重新開始正式競爭鎖萌壳,同樣,如果競爭不到還是會將自己沉睡日月,等待喚醒重新開始競爭袱瓮。
二、接下來我們看看signal方法爱咬,firstWaiter為condition自己維護的一個鏈表的頭結點尺借,取出第一個節(jié)點后開始喚醒操作
(1)著重看一下doSignal()
(2)繼續(xù)往下走,將老的頭結點精拟,加入到AQS的等待隊列中燎斩,這就是上邊賣的關子。你一定恍然大悟吧蜂绎?線程2發(fā)出signal信號后栅表,線程1就具備競爭鎖的條件了,這時候線程1就會被喚醒加入到AQS隊列中荡碾;
可以看到谨读,正常情況 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)這個判斷是不會為true的,所以坛吁,不會在這個時候喚醒該線程劳殖。只有到發(fā)送signal信號的線程用reentrantLock.unlock()后因為它已經(jīng)被加到AQS的等待隊列中,所以才會被喚醒拨脉。
接下來總結一下流程:
線程waitThread 先執(zhí)行調(diào)用reentrantLock.lock哆姻,獲取到了鎖,此時線程signalThread也去申請鎖時候被加入到AQS的等待隊列中玫膀。
線程waitThread調(diào)用await方法時釋放了鎖矛缨。
接著waitThread馬上被加入到Condition的等待隊列中,意味著該線程需要signal信號。
線程signalThread箕昭,因為線程waitThread釋放鎖的關系灵妨,被喚醒,并判斷可以獲取鎖落竹,于是線程signalThread獲取鎖泌霍。
線程signalThread調(diào)用signal方法,這個時候Condition的等待隊列中只有線程waitThread一個節(jié)點述召,于是它被喚醒朱转,去競爭鎖,此時線程signalThread還未釋放鎖积暖,所以只能加入到AQS的等待隊列中藤为。
signal方法執(zhí)行完畢,線程signalThread調(diào)用reentrantLock.unLock()方法夺刑,釋放鎖缅疟。這個時候因為AQS中只有線程waitThread,于是遍愿,AQS釋放鎖后按從頭到尾的順序喚醒線程時窿吩,線程waitThread被喚醒,于是線程waitThread繼續(xù)執(zhí)行错览。
直到釋放所整個過程執(zhí)行完畢纫雁。
總的來看:整個過程是AQS 和Condition這哥倆的等待隊列相互移動處理來實現(xiàn)的,Condition做為條件類倾哺,內(nèi)部維護了一個等待隊列轧邪,在合適的機會將自己的隊列的節(jié)點移除并將設置到AQS隊列中,讓其去爭奪lock羞海,Condition擁有修改AQS隊列的特權忌愚。