AQS
- 前言
- 阻塞的线程醒过来了
- checkInterruptWhileWaiting
-
- [interruptMode = 0](#interruptMode = 0 "#interruptMode__0_105")
- [interruptMode != 0](#interruptMode != 0 "#interruptMode__0_146")
- transferAfterCancelledWait
- await
- 总结
前言
上篇文章 中,我们分析了同步队列的节点如何new出条件队列,条件队列节点又是如何跑到同步队列中去的。
而在分析同步队列的节点如何new出条件队列的时候,我们从 await方法开始分析,直到了 isOnSyncQueue方法。而本篇内容将接着继续分析,挂起的线程唤醒后的过程。
cpp
public final void await() throws InterruptedException {
// 如果当前线程被中断过, 则直接抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
// 封装当前线程, 并扔到条件队列中
Node node = addConditionWaiter();
// 完全释放当前线程占用的锁, 并保存释放前(即当前)的锁状态
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果当前节点(封装好的线程)不在同步队列中
// 说明还没有被signal过
while (!isOnSyncQueue(node)) {
// 挂起
LockSupport.park(this);
// 讲道理, 上面执行后不应该执行到这里的
// 当如果线程被中断了、或者被signal了,
// 则会跑到这里
// 检查换新的原因, 如果是中断则跳出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 下面这部分代码先不用管
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
阻塞的线程醒过来了
当条件队列的节点不在同步队列时,我们接下的操作是计划将当前线程挂起,既然挂起了,下面的程序又是什么时候执行呢?
cpp
while (!isOnSyncQueue(node)) {
// 挂起
LockSupport.park(this);
// 讲道理, 上面执行后不应该执行到这里的
// 当如果线程被中断了、或者被signal了,
// 则会跑到这里
// 检查唤醒的原因, 如果是中断则跳出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
这里有两种情况:
1.没有中断、有对应的ConditionObject调用了signal方法进行唤醒操作。
- 休眠的线程被中断了、被迫唤醒。
checkInterruptWhileWaiting
所以,这里既然醒了,我们需要检查一下醒来的原因,以便判断接下的路该如何走。于是就有了这一行代码的判断:
cpp
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
关于checkInterruptWhileWaiting方法的源码如下,它会判断当前线程是否发生中断,没有的话取值为0(被正常的signal),有的话,需要检查一下中断的原因。
cpp
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ?
THROW_IE : REINTERRUPT) : 0;
}
interruptMode = 0
先说说interruptMode 为0的情况吧,如果为0,没有中断,继续外层 的判断,即while (!isOnSyncQueue(node))
为什么需要上面的判断呢?根据我们前面对signal方法的分析,唤醒的线程可能还没来得及跑到同步队列中,也就是说线程可能处于"假唤醒"的状态,处理的方式也比较简单,就加上while (!isOnSyncQueue(node))的循环判断,正常醒来的线程没有在同步队列中,那就仍然在等待队列中,继续让它睡觉。
如果已经在同步队列中了,跳出while循环、然后执行如下部分的源码,前面分析acquireQueued时候,我们可以知道它就是 阻塞式的获取锁。
如果acquireQueued返回false表示没有发生中断,如果返回true则表示在抢锁的过程中发生了中断。当然了,由于interruptMode == 0这里interruptMode = REINTERRUPT会被执行到。
cpp
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
接着判断如果node.nextWaiter不为null,会清除已经处于Cancel状态的节点。最后,在源码中进行自我中断。
cpp
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
interruptMode != 0
interruptMode != 0表示有中断发生,根据如下源码的注释,我们可以知道,中断也是需要分为两种情况的,一种是在唤醒signal之前发生中断,此时将interruptMode 设置为THROW_IE ,另一种则是在signal之后(已发生signal)发生中断,此时将interruptMode 设置为REINTERRUPT 。
这里可能比较难理解,很多人觉得如果是signal后,那么Thread.interrupted() ?判断时不应该返回0吗。其实这种思考陷入了一个单线程的思维模式里。多线程之下,A调用signa和B发生中断,是两个维度的东西,没有商量的情况下,谁也不能保证B的中断是发生在A的signal之前、还是之后。
关于interruptMode,先做一下总结,它初始值为0,它的取值有三个:
- REINTERRUPT:1 // 表示线程从等待退出后需要弥补一下中断
- THROW_IE :-1 // 表示线程从等待退出后需要抛出中断
- 0:没有中断
😃 说的啥意思啊????
cpp
/* Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/* Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
transferAfterCancelledWait
为了回答上述问题,我们可以接着看transferAfterCancelledWait方法,首选,能进入transferAfterCancelledWait这个方法,说明前面的Thread.interrupted()的判断为true,表示发生了中断,但发生中断后,如何判断中断发生在signal之前、还是之后呢?
如果是在signal之前发生的中断,那么肯定还没进入同步队列。那么我们是否可以通过判断同步队列是否存在该节点来达到目的呢?
我觉得可以,不过这种方法复杂度得O(n)吧~而源码中通过compareAndSetWaitStatus方法(CAS操作)试图将CONDITION状态置为0,如果成功,说明是在signal之前发生的中断(signal之后状态已经被修改了)
cpp
/**
* Transfers node, if necessary, to sync queue after a cancelled wait.
* Returns true if thread was cancelled before being signalled.
*
* @param node the node
* @return true if cancelled before the node was signalled
*/
final boolean transferAfterCancelledWait(Node node) {
// 如果能通过CAS成功将等待状态置为0, 说明中断在signal之前
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 进入到同步队列中
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
// 执行到这里说明上面的CAS不成功
// 说明中断发生在signal之后
while (!isOnSyncQueue(node))
// 线程让步
Thread.yield();
return false;
}
既然CAS不成功,说明中断发生在signal之后,但我还是得 while (!isOnSyncQueue(node))再判断一下,前面已经说了,signal发生时,不是立刻就到达同步队列的,如果同步队列没有,Thread.yield()这里可以理解为先让一下步,缓一缓,等节点到了同步队列,我再返回。
await
transferAfterCancelledWait方法分析完毕,再回到await方法下面这一行代码,此时interruptMode 就不会为0了,即跳出while循环了。
cpp
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
此时继续往下,执行这部分代码,当interruptMode为THROW_IE,表示signal之前发生的中断,此时在中断报告中抛出了异常。如果为REINTERRUPT,我们在报告中进行自我中断。这一点和我们分析acquire的时候有点儿像。
究其根本原因都是Thread.interrupted()并不会真的中断,只是进行判断、返回结果并清除中断标识。真正是否中断交由顶层接口进行操作。
cpp
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
cpp
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
总结
本文主要分析await后挂起的线程醒来时,接下来的运行流程。由于醒来时,程序并不知道原因所以需要进行判断。
而醒来的原因又可以分为signal自然唤醒、中断(被迫唤醒)。其中,中断导致的唤醒,又可以分为signal前中断和signal后中断,针对这些情况,本文都做了分析。
(完结)