概述
- 条件队列在 并发编程中是用于对一类事务在处理时机未到的情况下,让负责处理此类事务的线程进行阻塞,当时机成熟的时候,将其唤醒,使其继续往下处理这件事务;
- 条件队列针对阻塞于某类条件的线程进行集中化的队列管理,能保证当该条件成熟了 ,这些线程能够按照有序的方式一个个转换到同步队列中;
- 需要区分两种唤醒,条件队列的唤醒,是每次唤醒后会将其转换到同步队列,最终这些条件队列上的CONDITION节点都是转换到同步队列中的;同步队列的唤醒,是去争夺处理机,真正去执行线程内容;所以,第一个唤醒不考虑特殊情况的话表明条件成熟了,线程就绪;第二个唤醒同样不考虑特殊情况的话表明前置节点执行完了,准备将处理机让给它,线程运行;
ConditionObject类内容说明
public class ConditionObject implements
Condition, java.io.Serializable {
// 条件队列的第一个waiter
private transient Node firstWaiter;
// 条件队列的最后一个waiter
private transient Node lastWaiter;
/**
* 表示重新中断退出wait模式
*/
private static final int REINTERRUPT = 1;
/**
* 表示抛出 InterruptedException 退出等待模式
*/
private static final int THROW_IE = -1;
}
条件队列图
上方双指针链表队列 为同步队列,下方单指针链表 队列为条件队列;
条件队列进队流程描述
- 判断当前线程是否已经中断,已经中断的话在game over;
- 创建节点并从尾部进入条件队列,这里如果队列lastWaiter的waitStatus为CANCELLED,则会先对队列进行整理再进行新节点入队;
- await/signal 操作的前提是获取到了锁,所以await操作在节点入队之后需要对锁进行释放,释放失败的话将节点的状态设置为CANCELLED,这也是2中为什么只针对lastWaiter的状态进行判断的原因;
- 入队、锁释放都已完成,下一步就是需要对线程进行阻塞,阻塞的前提是节点目前不在同步队列中;阻塞的伪代码如下:
while(不在同步队列) {
park(this)
//~
// 苏醒之后继续旋转判断,合适就退出
}
- 在第4点说了,唤醒之后会在自旋里判断是否需要继续park,但这个唤醒还不太好搞,可能是中断,也可能是signal,所以这里需要鉴别一下这两种情况。鉴别方式是,如果CAS将条件队列节点状态从CONDITION转为0,并且成功 (条件队列在成功的情况下会 将条件节点转移至同步队列) ,则说明中断发生在signal前;如果CAS失败,则说明signal发生在中断之前,因为节点 状态已经非CONDITION; 伪代码修改如下:
while(不在同步队列) {
park(this)
//~
// 苏醒之后继续旋转判断,合适就退出
if (发生中断,验证是中断发生在signal之前还是signal发生在中断之前) {
break;
}
}
- 节点此时已经处于同步队列,则需要走AQS独占模式的acquireQueued()方法,该方法是一个自旋,如果前节点不是头节点,则直接park,当被unparkSecussor后,继续在循环体里去尝试获取锁,成功则跳出执行,不成功则继续park;
- 节点传送至同步队列之后,根据该节点的nextWaiter是否为空去判断是否需要对条件队列的节点关系进行调整,对CANCELLED节点进行剔除;这里可能需要调整是因为会出现下图这种情况:
- 根据acquireQueued()返回的中断bool值和步骤5判断出来的中断类型决定是否需要throw InterruptException或者interrupt()还原中断信号;如果步骤5的判断结果是中断发生在signal 前,则需要throw InterruptException;如果步骤5的判断结果是(中断发生在signal 后或者没中断),并且acquireQueued() 返回为true,则需要调用interrupt()还原中断状态;
条件队列进队流程图解
条件队列进队源码解析
1、await()
public final void await() throws InterruptedException {
/**
* 1、如果当前线程被中断, 则抛出InterruptException
*/
if (Thread.interrupted())
throw new InterruptedException();
/**
* 2、waiter 入队
* 两件事情:1、调整条件队列关系;2、节点从尾部入队;
*/
Node node = addConditionWaiter();
/**
* 3、await()是阻塞操作,并且调用await之前是需要获取到锁;
* 所以线程入队之后需要释放已获取的锁,如果失败的话则
* 节点入队状态为CANCELLED;
*/
int savedState = fullyRelease(node);
/**
* 4、走到这里节点已经入队成功,这一步的作用是校验节点相应
* 的条件是否已经就绪,如果已经就绪(signal)或者被中断
* interrupt的话条件节点会被移动到同步队列;
*/
int interruptMode = 0