zoukankan      html  css  js  c++  java
  • Condition的await-signal流程详解

           上一篇讲了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()至阻塞的过程

    如图所示,该过程可分为三个步骤:

    1. 新建Condition Node包装线程,加入Condition队列
    2. 释放当前线程占用的锁
    3. 阻塞当前线程
    在阻塞当前线程之前,要判断Condition Node是否在release队列里。如果在的话则没必要阻塞,可直接参与锁竞争。关键代码如下:
    1. // AbstractQueuedSynchronizer.ConditionObject.class  
    2.   
    3. final boolean isOnSyncQueue(Node node) {  
    4.     // 当进入Condition队列时,waitStatus肯定为CONDITION,如果同时别的线程调用signal,Node会从Condition队列中移除,并且移除时会清除CONDITION状态。  
    5.     // 从移除到进入release队列,中间这段时间prev必然为null,所以还是返回false,即被park  
    6.     if (node.waitStatus == Node.CONDITION || node.prev == null)  
    7.         return false;  
    8.     // 当别的线程进入release队列时,会和前一个Node建立前后关系,所以如果next存在,说明一定在release队列中  
    9.     if (node.next != null) // If has successor, it must be on queue  
    10.         return true;  
    11.     /* 
    12.      * node.prev can be non-null, but not yet on queue because 
    13.      * the CAS to place it on queue can fail. So we have to 
    14.      * traverse from tail to make sure it actually made it.  It 
    15.      * will always be near the tail in calls to this method, and 
    16.      * unless the CAS failed (which is unlikely), it will be 
    17.      * there, so we hardly ever traverse much. 
    18.      */  
    19.     // 可能该Node刚刚最后一个进入release队列,所以是tail,其next必然是null,所以需要从队尾向前查找  
    20.     return findNodeFromTail(node);  
    21. }  
    // 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没被取消,但是入队列的时候也可能会被取消,因此代码对这个情况做了点特殊处理。我根据自己的理解将代码做了如下解释:
    1. // AbstractQueuedSynchronizer.ConditionObject.class  
    2.   
    3. final boolean transferForSignal(Node node) {  
    4.     /* 
    5.      * If cannot change waitStatus, the node has been cancelled. 
    6.      */  
    7.     // 如果改变waitStatus失败,说明已经被取消,没必要再进入release队列了。外部再循环找到一个Condition Node  
    8.     // 如果改变waitStatus成功,但是之后又被取消会怎么样?没关系,虽然已经进入release队列了,但是release方法里的unpark操作会跳过已取消的Node。这里的检查只是为了减少unpark时不必要的工作  
    9.     if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))  
    10.         return false;  
    11.   
    12.     /* 
    13.      * Splice onto queue and try to set waitStatus of predecessor to 
    14.      * indicate that thread is (probably) waiting. If cancelled or 
    15.      * attempt to set waitStatus fails, wake up to resync (in which 
    16.      * case the waitStatus can be transiently and harmlessly wrong). 
    17.      */  
    18.     // p是该Node的前驱  
    19.     Node p = enq(node);  
    20.     int ws = p.waitStatus;  
    21.     // 这里影响设置waitStatus只可能发生于线程被取消,那时会调用cancelAcquire方法将waitStatus设置为CANCEL,但它不是CAS的  
    22.     if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))  
    23.         LockSupport.unpark(node.thread);  
    24.     return true;  
    25. }  
    // 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队列中,仅此而已。
    之后有时间我会把一些中断处理也用流程图描述下发出来。
  • 相关阅读:
    海尔大数据精准营销平台(内部资料)
    马化腾做的PPT:产品设计与用户体验
    网站上线后,第一次完成线上线下整个环节
    灵感不断
    redis命令
    Redis持久化实践及灾难恢复模拟
    [转]创业公司常见的25个法律问题
    用python语言编写网络爬虫
    Python3常用网络编程模块介绍
    Python3数据库模块(sqlite3,SQLite3)
  • 原文地址:https://www.cnblogs.com/xuxiuxiu/p/6835568.html
Copyright © 2011-2022 走看看