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 转移成功;