程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Condition的await-signal流程詳解,javaconditionawait

Condition的await-signal流程詳解,javaconditionawait

編輯:JAVA綜合教程

Condition的await-signal流程詳解,javaconditionawait


轉載請注明出處:http://blog.csdn.net/luonanqin

 

 

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

 

Condition類圖:

 

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

 

       和release隊列一樣,Condition隊列也是虛擬隊列,每個Node通過nextWaiter進行關聯。因為Condition Node要變為release Node才可以解除阻塞,所以不需要prevWaiter,這一點下面會有說明。

大概的整個過程是:

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

 

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

 

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

在阻塞當前線程之前,要判斷Condition Node是否在release隊列裡。如果在的話則沒必要阻塞,可直接參與鎖競爭。關鍵代碼如下:
// AbstractQueuedSynchronizer.ConditionObject.class  
  
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隊列中,僅此而已。

之後有時間我會把一些中斷處理也用流程圖描述下發出來。

 

參考資料:

怎麼理解Condition   http://www.liuinsect.com/2014/01/27/how_to_understand_condition/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved