溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

類似Object監(jiān)視器方法的Condition接口(詳解)

發(fā)布時間:2020-10-09 16:06:06 來源:腳本之家 閱讀:172 作者:jingxian 欄目:編程語言

在《基于線程、并發(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é)點)

類似Object監(jiān)視器方法的Condition接口(詳解)

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

  類似Object監(jiān)視器方法的Condition接口(詳解)

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

類似Object監(jiān)視器方法的Condition接口(詳解)

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)轉(zhuǎn)換為在同步隊列中,并且也已經(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)容了,希望能給大家一個參考,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI