類似Object監(jiān)視器方法的Condition接口(詳解)
在《基于線程、并發(fā)的基本概念(詳解)》中,我們利用synchronized關鍵字、Queue隊列、以及Object監(jiān)視器方法實現(xiàn)了生產(chǎn)者消費者,介紹了有關線程的一些基本概念。Object類提供的wait的方法和notifyAll方法,與之對應的是Condition接口提供是await和signalAll。await(或wait)是讓當前線程進入等待狀態(tài)并釋放鎖,signalAll(或notifyAll)則是喚醒等待中的線程,使得等待中的線程有競爭鎖的資格,注意只是資格,并不代表被喚醒的線程就一定會獲得鎖。
Condition接口的具體實現(xiàn)還是在AbstractQueuedSynchronizer中的內(nèi)部實現(xiàn)的——AbstractQueuedSynchronizer$ConditionObject。ConditionObject中維護了一個“等待隊列”,注意這個和AQS同步器維護的“同步隊列”不同。AQS所維護的同步隊列是當前等待資源(同步狀態(tài))的隊列,當前線程獲取同步狀態(tài)失敗時,同步器會將當前線程以及等待狀態(tài)等信息構造成一個節(jié)點并加入到同步隊列中,同時阻塞當前線程,當同步狀態(tài)被所持有的線程釋放時會將同步隊列中的首節(jié)點喚醒重新獲取同步狀態(tài)。而每個Condition維護一個等待隊列,該隊列的作用是一個等待signal信號的隊列。這兩者之間的關系是一個協(xié)同的關系,用下圖的說明它們之間的協(xié)同過程:
1. AQS的同步隊列如下圖所示,一個頭結點head指向隊首,一個tail指向隊尾,當線程調(diào)用lock()方法獲取鎖而未成功時,線程被構造成節(jié)點加入到隊尾。(圖中NodeA是同步隊列的第一個節(jié)點,也就是獲得同步狀態(tài)的節(jié)點)

2.NodeA調(diào)用await()方法時,NodeA從AQS同步隊列中移除,自然也就釋放了鎖,NodeA此時被加入到Condition的等待隊列中,等待signal信號,如下圖所示。

3.執(zhí)行完第2步后,此時NodeB在同步隊列中處于第一個節(jié)點位置,即獲取到了鎖,如果NodeB此時執(zhí)行signal(或者signalAll)方法,NodeA將會從Condition等待隊列中被移除即被喚醒,加入到同步隊列中,此時NodeA僅僅是被喚醒有了在同步隊列中爭奪資源的資格,并不代表被喚醒后就立即獲得鎖,如下圖所示。

4. 最后NodeB在signal執(zhí)行完畢后,調(diào)用unLock方法釋放鎖,此時NodeA處于隊首,并爭奪同步狀態(tài)。
以上是AQS的“同步隊列”和Condition的“等待隊列”之間相互協(xié)作的過程,下面從源碼解析Condition的主要方法await、signal、signalAll。
public final void await() throws InterruptedException{
if (Thread.interrupted()) //線程被中斷則拋出中斷異常
throw new InterruptedException();
Node node = addConditionWaiter(); //將線程構造為Node節(jié)點
long savedState = fullyRelease(node); //釋放鎖,返回同步狀態(tài)
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //循環(huán)判斷當前節(jié)點是否在同步隊列中
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; //檢查節(jié)點在處于等待狀態(tài)時是否被中斷
}
//在跳出了循環(huán),即被signal喚醒后重新加入了同步隊列后,開始重新競爭鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //acquireQueued自旋獲取鎖,具體分析見《2.從AbstractQueuedSynchronizer(AQS)說起(1)——獨占模式的鎖獲取與釋放》中對獲取同步狀態(tài)的解析
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); //如果節(jié)點從等待狀態(tài)轉換為在同步隊列中,并且也已經(jīng)獲得了鎖,此時將斷開此節(jié)點后面的等待節(jié)點
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
在獲取鎖的線程調(diào)用await時,首先會將線程構造為Node節(jié)點并釋放鎖,此時線程被移出同步隊列加入到Condition等待隊列中,接著在第7行就會while循環(huán)判斷節(jié)點是否在同步隊列中,當沒有線程調(diào)用signal方法的時候顯然線程不在同步隊列,并將一直循環(huán),直到有線程調(diào)用signal方法該線程才會被喚醒加入到同步隊列中,此時才會跳出循環(huán)。
signal和signalAll方法的異同在和notify和notifyAll一樣。signal只會喚醒等待隊列中位于隊首的節(jié)點使其具有競爭鎖的資格,而signalAll則會喚醒等待隊列中所有節(jié)點使所有節(jié)點都具有競爭鎖的資格。
public final void signal() {
if (!isHeldExclusively()) //判斷當前線程是否持有鎖
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); //喚醒等待隊列中的第一個節(jié)點
}
對比signalAll方法,不同點在于第6行是喚醒等待隊列中的所有節(jié)點——doSignalAll(first),不再貼出代碼。
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null) //transferForSignal方法將處于等待隊列中的節(jié)點添加到同步隊列中
}
至于doSignalAll則是循環(huán)調(diào)用transferForSignal使得所有節(jié)點都被喚醒加入到同步隊列中。
當節(jié)點從等待隊列中加入到同步隊列中時,呼應await中的循環(huán)等待節(jié)點是否在同步隊列中,await和signal的協(xié)同配合也就很清晰明了了。
以上這篇類似Object監(jiān)視器方法的Condition接口(詳解)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java 添加數(shù)字簽名到excel及檢測,刪除簽名
這篇文章主要介紹了Java 添加數(shù)字簽名到excel及檢測,刪除簽名的方法,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下2021-04-04
Java的接口調(diào)用時的權限驗證功能的實現(xiàn)
這篇文章主要介紹了Java的接口調(diào)用時的權限驗證功能的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
SpringBoot整合MyBatis逆向工程及 MyBatis通用Mapper實例詳解
這篇文章主要介紹了SpringBoot整合MyBatis逆向工程及 MyBatis通用Mapper實例詳解 ,需要的朋友可以參考下2017-09-09
java 使用ConcurrentHashMap和計數(shù)器實現(xiàn)鎖
這篇文章主要介紹了java 使用ConcurrentHashMap和計數(shù)器實現(xiàn)鎖的相關資料,需要的朋友可以參考下2017-05-05
java使用poi在excel單元格添加超鏈接設置字體顏色的方法
這篇文章主要介紹了java使用poi在excel單元格添加超鏈接,設置字體顏色,poi功能還是很強大的,基本能想到的功能都能通過poi實現(xiàn),本文結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-09-09

