zoukankan      html  css  js  c++  java
  • 源码分析-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 转移成功;

  • 相关阅读:
    Ext JS学习第三天 我们所熟悉的javascript(二)
    Ext JS学习第二天 我们所熟悉的javascript(一)
    Ext JS学习第十七天 事件机制event(二)
    Ext JS学习第十六天 事件机制event(一)
    Ext JS学习第十五天 Ext基础之 Ext.DomQuery
    Ext JS学习第十四天 Ext基础之 Ext.DomHelper
    Ext JS学习第十三天 Ext基础之 Ext.Element
    Ext JS学习第十天 Ext基础之 扩展原生的javascript对象(二)
    针对错误 “服务器提交了协议冲突. Section=ResponseHeader Detail=CR 后面必须是 LF” 的原因分析
    C# 使用HttpWebRequest通过PHP接口 上传文件
  • 原文地址:https://www.cnblogs.com/ttaall/p/13860331.html
Copyright © 2011-2022 走看看