Condition的await-signal流程詳解(轉)

上一篇文章梳理了condtion,其中側重流程,網上看到這篇文章文章介紹的很細。值得學習。特地轉載過來。java

 

轉載請註明出處:http://blog.csdn.net/luonanqinnode

轉載路徑:http://blog.csdn.net/bohu83/article/details/51107870less

 

       上一篇講了ReentrantLock的lock-unlock流程,今天這篇講講Condition的await-signal流程。學習

 

Condition類圖:ui

 

  • Condition接口包含了多種await方式和兩個通知方法
  • ConditionObject實現了Condition接口,是AbstractQueuedSynchronizer的內部類
  • Reentrantlock的newCondition方法返回與某個lock實例相關的Condition對象

 

       和release隊列同樣,Condition隊列也是虛擬隊列,每一個Node經過nextWaiter進行關聯。由於Condition Node要變爲release Node才能夠解除阻塞,因此不須要prevWaiter,這一點下面會有說明。

大概的整個過程是:this

       調用await的線程都會進入一個Condition隊列。調用signal的線程每一次都會從firstWaiter開始找出未取消的Condition Node放到release隊列裏,而後調用signal的線程在await或者unlock的時候執行release方法纔有機會將其解除阻塞。相對於lock-unlock,正常的流程要簡單一些,可是對於中斷處理會更爲複雜。spa

 

先看看調用await()至阻塞的過程.net

 

如圖所示,該過程可分爲三個步驟:線程

  1. 新建Condition Node包裝線程,加入Condition隊列
  2. 釋放當前線程佔用的鎖
  3. 阻塞當前線程
在阻塞當前線程以前,要判斷Condition Node是否在release隊列裏。若是在的話則不必阻塞,可直接參與鎖競爭。關鍵代碼以下:
 

// AbstractQueuedSynchronizer.ConditionObject.class  code

final boolean isOnSyncQueue(Node node) {  

// 當進入Condition隊列時,waitStatus確定爲CONDITION,若是同時別的線程調用signal,Node會從Condition隊列中移除,而且移除時會清除CONDITION狀態。  

// 從移除到進入release隊列,中間這段時間prev必然爲null,因此仍是返回false,即被park  

if (node.waitStatus == Node.CONDITION || node.prev == null)  

return false;  

// 當別的線程進入release隊列時,會和前一個Node創建先後關係,因此若是next存在,說明必定在release隊列中  

if (node.next != null) // If has successor, it must be on queue  

return true;  

/* 

     * node.prev can be non-null, but not yet on queue because 

     * the CAS to place it on queue can fail. So we have to 

     * traverse from tail to make sure it actually made it.  It 

     * will always be near the tail in calls to this method, and 

     * unless the CAS failed (which is unlikely), it will be 

     * there, so we hardly ever traverse much. 

     */  

// 可能該Node剛剛最後一個進入release隊列,因此是tail,其next必然是null,因此須要從隊尾向前查找  

return findNodeFromTail(node);  

}  

 

 


signal()流程圖
 
 
       signal方法更簡單一些,就是從firstWaiter開始,找到一個沒有取消的Node放入release隊列。可是即便一開始找到的Node沒被取消,可是入隊列的時候也可能會被取消,所以代碼對這個狀況作了點特殊處理。我根據本身的理解將代碼作了以下解釋:
 

// AbstractQueuedSynchronizer.ConditionObject.class  

final boolean transferForSignal(Node node) {  

/* 

     * If cannot change waitStatus, the node has been cancelled. 

     */  

// 若是改變waitStatus失敗,說明已經被取消,不必再進入release隊列了。外部再循環找到一個Condition Node  

// 若是改變waitStatus成功,可是以後又被取消會怎麼樣?不要緊,雖然已經進入release隊列了,可是release方法裏的unpark操做會跳過已取消的Node。這裏的檢查只是爲了減小unpark時沒必要要的工做  

if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))  

return false;  

/* 

     * Splice onto queue and try to set waitStatus of predecessor to 

     * indicate that thread is (probably) waiting. If cancelled or 

     * attempt to set waitStatus fails, wake up to resync (in which 

     * case the waitStatus can be transiently and harmlessly wrong). 

     */  

// p是該Node的前驅  

    Node p = enq(node);  

int ws = p.waitStatus;  

// 這裏影響設置waitStatus只可能發生於線程被取消,那時會調用cancelAcquire方法將waitStatus設置爲CANCEL,但它不是CAS的  

if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))  

        LockSupport.unpark(node.thread);  

return true;  

}  

 

       咱們能夠看到,signal方法只是將Node修改了狀態,並無喚醒線程。要將修改狀態後的Node喚醒,一種是再次調用await(),一種是調用unlock()。這兩個方法內部都會執行release方法對release隊列裏的Node解除阻塞,關於這點我在上一篇文章裏已經說明了。
 
下面我把調用 await()的線程被解除阻塞後的流程也畫了一下:
 
以上就是await和signal的詳細流程。signalAll和signal很像,內部就是將Condition隊列裏全部的Node都加入到release隊列中,僅此而已。 以後有時間我會把一些中斷處理也用流程圖描述下發出來。
相關文章
相關標籤/搜索