源码分析-Condition结合AQS解析

Condition  

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

主要方法就是 await()  :使当前持有锁的线程进入等待

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

      

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

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

常规用法

生产住阻塞

 

消费者唤醒生产者

 

深入源码 深入~

补充一下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()无参方法实现   实现了当前持有锁的线程阻塞,无法继续进行,并释放锁

 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){

  自我中断

}

猜你喜欢

转载自blog.csdn.net/a159357445566/article/details/109230522