条件队列的结构以及await()/signal()的使用
等待队列由AQS内部的ConditionObject实现,它实现了Condition接口的方法await()/signal()等,ConditionObject内部维护了一个头节点和一个尾节点,利用AQS内部Node节点的nextWaiter实现了单向等待队列。
条件队列的产生,是由于同步队列中获取到资源的节点由于某种条件被挂起从而加入到条件队列等待唤醒,下面就await()和signal()来进行着手分析。
public final void signal() {
// 如果不是被独自持有的则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取条件队列中第一个节点
Node first = firstWaiter;
// 执行doSignal()函数
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 将执行transferForSignal()函数成功则退出该循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
// 修改当前节点状态为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将条件队列节点入队同步队列并返回前置节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果前置节点被取消或者设置设置为Node.SIGNAL状态失败
// 唤醒当前线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
整个signal()的过程还是比较简单的,当调用signal()函数时,会获取条件队列中的第一个节点,然后执行doSignal()函数,并将firstWaiter更改为first.nextWaiter,最后执行transferForSignal()函数将当前节点替换到同步队列末尾(如果前置节点被取消或设置SIGNAL状态失败则当前节点直接唤醒)。
再来看看await()函数的执行流程,
public final void await() throws InterruptedException {
// 在执行await()函数过程中被中断
if (Thread.interrupted())
throw new InterruptedException();
// 添加至条件队列
Node node = addConditionWaiter();
// 释放原同步队列持有该线程引用的节点
// 释放资源
int savedState = fullyRelease(node);
// 打断的模式
int interruptMode = 0;
// 如果当前在条件队列中
// 直接挂起当前线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 判断interruptMode 在挂起过程中是否被唤醒或者中断
// 如果被打断,直接响应中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 获取资源的过程中被中断过并且interruptMode不为THROW_IE
// 将interruptMode更换为REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 执行到这一步说明被唤醒的节点已经成功获取到资源
// 判断等待资源过程中是否被中断以响应中断
// 清空当前条件队列中被取消的节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 响应中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
条件队列只会在独享模式下存在,所以await()方法里面直接调用acquireQueued()。
线程在同步队列与条件队列之间的转换
- 当前资源被一个线程持有,此时来了一个A线程与B线程
- 当资源被释放后A获取到资源
- 此时A由于某个条件不满足,被await()到条件队列中,并fullyRelease资源唤醒B,B成功获取到资源
- B在执行过程中来了一个C线程想要获取资源被阻塞
- 此时B也由于某种条件不满足被加入到条件队列,C被唤醒竞争到资源
- C执行到signal()时唤醒条件队列中的第一个节点,并加入到同步队列,执行acquireQueued尝试获取资源,此时C还在持有资源
- 此时C执行完毕,A将继续执行之前await()之后的操作