Java多線程進階(八)—— J.U.C之locks框架:AQS的Conditon等待(3)

cc4526577154ea2623f01f0258b6765a.jpg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

1、本章概述

本章將繼續以ReentrantLock的調用爲例,說明AbstractQueuedSynchronizer提供的Conditon等待功能。關於Conditon接口的介紹,能夠參見:Java多線程進階(二)—— juc-locks鎖框架:接口segmentfault

2、Condition接口的實現

J.U.C包提供了Conditon接口,用以對原生的Object.wait()Object.notify()進行加強。多線程

Condition接口的實現類實際上是在AQS中——ConditionObject,ReentranLock的newConditon方法實際上是建立了一個AbstractQueuedSynchronizer.ConditionObject對象:
clipboard.png框架

Condition做爲AQS的內部類,複用了AQS的結點,維護一個條件隊列,隊列初始時的結構以下:
clipboard.pngui

clipboard.png

示例

假設如今有3個線程:ThreadA、ThreadB、ThreadC,一個Conditon實現對象。
ReentrantLock lock = new ReentrantLock();
Conditon con = lock.newConditon();

線程將以如下的時序調用:spa

//ThreadA先調用lock方法獲取到鎖,而後調用con.await()

//ThreadB獲取鎖,調用con.signal()喚醒ThreadA

//ThreadB釋放鎖

1. ThreadA獲取到鎖後,首先調用await方法

clipboard.png

上述方法,先對線程中斷作一次預判斷,而後將線程包裝成結點插入【條件隊列】,插入完成後,條件隊列的結構以下:線程

clipboard.png

咱們知道,await()方法會釋放當前線程持有的鎖,這個過程其實就是fullyRelease方法的做用:
clipboard.png3d

而後,判斷當前結點是否是在【等待隊列】中,不在的話就會阻塞線程。
最終線程A釋放了鎖,並進入阻塞狀態。調試

2. ThreadB獲取到鎖後,首先調用signal方法

因爲Condition的signal方法要求線程必須得到與此Condition對象相關聯的鎖,因此這裏有箇中斷判斷:
clipboard.pngcode

而後,會調用doSignal方法,刪除條件隊列中的隊首CONDITION類型結點:
clipboard.png對象

刪除完成後,transferForSignal方法會將CONDITON結點轉換爲初始結點,並插入【等待隊列】:
clipboard.png

此時,【條件隊列】已經空了:
clipboard.png

而ThreadA被包裝成新結點後,插入【等待隊列】:
clipboard.png

3. ThreadB釋放鎖

終於ThreadB釋放了鎖,釋放成功後,會調用unparkSuccessor方法(參加AQS獨佔功能的講解),喚醒隊列中的首結點:
clipboard.png

最終等待隊列結構以下:
clipboard.png

4. ThreadA從喚醒處繼續執行

ThreadA被喚醒後,從await方法的阻塞處開始繼續往下執行:
clipboard.png

以後會調用acquireQueued方法再次嘗試獲取鎖,獲取成功後,最終等待隊列狀態以下:

clipboard.png

3、總結

本章以ReentrantLock的公平鎖爲例,分析了AbstractQueuedSynchronizer的Condition功能。
經過分析,能夠看到,當線程在指定Condition對象上等待的時候,其實就是將線程包裝成結點,加入了條件隊列,而後阻塞。當線程被通知喚醒時,則是將條件隊列中的結點轉換成等待隊列中的結點,以後的處理就和獨佔功能徹底同樣。

除此以外,Condition還支持限時等待、非中斷等待等功能,分析思路是同樣的,讀者能夠本身去閱讀AQS的源碼,經過使用示例,加入調試斷點一步步看內部的調用流程,主幹理順了以後,再看其它分支,實際上是殊途同歸的。

相關文章
相關標籤/搜索