• 源码分析-Condition结合AQS解析


    Condition  

     如图,java.util.concurrent.locks包下,与AQS同级

    主要方法就是 await()  :使当前持有锁的线程进入等待    (实际上是加入到Condition维护的一个条件队列去,然后挂起)

           signal() : 唤醒等待的线程重新排队去抢锁

          

    Condition常用于生产者消费者场景, 负责生产者消费者的阻塞用途

    不同于Object的 wait() notify() 是基于对象的监视器锁的, Condition是基于持有lock锁的线程,必须持有锁才能操作await()和signal()

    常规用法

    创建Lock 和 Condition

    生产者阻塞

     

    消费者唤醒生产者

     

    深入源码 深入~

    补充一下Node节点的重要属性

     waitStatus 代表了当前节点的后继节点的状态, CONDITION,CANCELLED,PROPAGETE,SIGNAL分别为几种状态

    CONDITION 值为-2 代表当前节点加入到了条件队列

    CANCELLED 值为1 代表当前节点线程取消抢锁  (以后会被清理出去)

    SIGNAL 值为-1 表示后继节点线程需要唤醒

    ReentrantLock的newCondition()入手

     AQS的内部类  ConditionObject

    重要属性:

    firstWaiter .条件队列的队头

    lastWaiter  条件队列的队尾

    Condition维护了一个条件队列,不同于AQS的阻塞队列Node是负责争抢锁,条件队列存储被阻塞释放了锁的Node

     await()方法,将节点入队到条件队列,signal()方法出队队头,入队到阻塞队列,排队去抢锁,抢到锁了之前的await()方法才能返回,才能继续进行

    源码分析从await()展开

    1.AQS的 await()无参方法实现   实现了当前持有锁的线程封装成Node,加入到条件队列,释放锁,中断线程

     if (一开始就判断该线程是否中断)  已经中断了就是直接抛出

    创建一个Node并 添加到Condition的条件队列;  //详见2

    释放锁 并保留释放锁之前的state值;  (为了signal后还原回去)    //详见3

    while (当前线程没有转移到阻塞队列 简单不做深入) {

      将当前线程挂起;

      if (是否有人中断了线程的挂起  //详见5 )  

        如果被中断 并且转移到了阻塞队列 退出循环。

    }

    if (再次中断当前节点,这次是在阻塞队列里中断了 && 之前的中断标志不是抛异常,说明是signal()后中断的){

      中断标志设置为REINTERRUPT,重置中断标志

    }

    if(节点的下一个等待节点不为空,说明还没和条件队列断开联系 ){  //造成原因 因为正常signal()会与条件队列直接断开联系,而中断发生在signal()前 也会加入到阻塞队列 造成没断开联系

      和条件队列断开联系;

    }

    if(中断标志不是0){

      抛异常或者再次中断当前线程,或者什么都不做;   //详见 7

    }

    2. addConditionWaiter()  将当前线程加入到条件队列

    Node t = 条件队列的队尾;

    if (队尾不为空 && 队尾state不是CONDITION){

      执行清理,清除条件队列中所有取消排队的节点

      t = 清理后的队尾

    }

    Node node = 创建一个当前线程的节点

    if (队尾是空了) {

      让刚创建的node 当头节点

    }else {

      node入到队尾

    }

    3. fullyRelease()  传入当前节点,释放掉持有的锁,返回之前state的值

      

    boolean failed = true; 默认失败

    try{

      获取当前state值  

      if (释放锁成功 ) {       //详见4

        返回释放前state值

      } 否则抛异常

    }finally {

      如果释放锁失败了 当前节点的waitStatus = 取消排队

    }

    4. 详细说一下release()  传入释放前state值  释放锁

     

     尝试去释放锁

    int c = 当前state - 传入的state

    if (如果持有锁的线程不是当前线程) 抛异常

    if (c = 0) {

      设置持有锁线程null

    }

    设置state为c

    返回成功释放或失败

    5. checkInterruptWhileWaiting() 传入当前节点,检查挂起的时候是否被中断

    如果线程await期间没有被中断则返回 0 

    如果线程被中断了,是在signal()唤醒前中断的 ,还是在之后中断的 

    唤醒前中断的 返回THROW_IE 抛出异常   表示自己是被signal()之前就中断的

    唤醒后中断的 返回REINTERRYPT  重新设置中断状态  (因为await期间没有中断,signal之后才发生的中断)

    6.  transferAfterCancellWait()  取消挂起后的转移

     if (尝试CAS设置当前节点的waitStatus为0  说明是signal()之前发生的中断,因为signal会设置waitStatus为0){

      即时发生中断了,也把当前节点加入到阻塞队列去

      返回true 转移成功

    }

    //到这里说明上边的CAS失败了,因为signal()已经把waitStatus置为0了

    while (当前节点不在阻塞队列上) {

      //可能是signal后还没转移到阻塞队列,在这循环等待一下
      Thread.yield();

    }

    return false;

    这里描绘了一个场景,本来有个线程,它是排在条件队列的后面的,但是因为它被中断了,那么它会被唤醒,然后它发现自己不是被 signal 的那个,但是它会自己主动去进入到阻塞队列。

    7.   reportInterruptAfterWait()  抛异常或者 再次终端当前线程,或者什么都不做

     if (中断标志==THROW_IE){

      抛中断异常

    }else if(中断标志==RENITERRUPT){

      自我中断

    }

    8. signal()  唤醒对应Condition条件队列里等待最久的那个节点(头节点),转移到阻塞队列  (signal()之前节点正处于await()的挂起状态)

    isHeldExclusively() 判断当前线程是否持有锁

    Node first = 条件队列头节点

    if (头节点 != null){

      doSignal(头节点); //转移头节点

    }

     传入头节点,  first 代表要转移的节点

    do{

      if (头节点的下一个节点null的时候) {    //这里是因为头节点即将转移,所以提前 将头节点的下一个节点作为头节点

        尾节点也置为null

      }  

      传入节点的下一个节点置空;   //切断与条件队列的联系

    }while (转移节点不成功 && 抛弃掉原来的转移节点,将新的头节点当作转移节点);

    9. transferForSignal()  转移传入的节点,从条件队列到阻塞队列

    if (将节点的CONDITION状态设置为0失败){   //说明节点在转移前取消的排队

      return 转移失败;

    Node p = enq(Node);   //enq() 自旋将节点加入到阻塞队列,返回值p 是在阻塞队列的前置节点

    if (前置节点的等待状态>0 || 不能CAS将状态修改为下一个节点需要被唤醒){   //说明如果前置节点放弃排队了,或者不能CAS修改前置节点的状态

      该节点线程取消挂起

    }

    return 转移成功;

  • 相关阅读:
    多态与多态性,鸭子类型
    类的继承与派生,抽象类
    常用模块
    模块与包
    三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数
    函数装饰器
    函数基础
    文件处理
    数据类型
    Spring源码分析--IOC流程
  • 原文地址:https://www.cnblogs.com/ttaall/p/13860331.html
Copyright © 2020-2023  润新知