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队列中,仅此而已。 以后有时间我会把一些中断处理也用流程图描述下发出来。
相关文章
相关标签/搜索