Java AbstractQueuedSynchronizer源码阅读5-从await()和awaitUninterruptibly()看interrupt

这篇文章说是对AbstractQueuedSynchronizer源码的阅读,倒不如说是对java interrupt的理解。

在看await()和awaitInterruptibly()的代码前,我们先来了解下java的中断机制。

java中断机制

本文对中断机制的理解参考了这篇文章详细分析Java中断机制。

  1. 为啥有Interrupt这个东西?
    因为存在这么个需求:一个线程去中断另一个线程。

  2. Interrupt是如何工作的?
    每个线程有一个中断标识,想要中断这个线程,可以将该线程的中断标识设置为true。该线程会在适当时机检查自己的中断标识,并决定如何处理中断。
    这种中断不具有强制性,应该属于软中断?

java里和中断有关的三个方法如下:

方法 作用
boolean interrupted() 测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)
boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响
void interrupt() 中断线程

await()和awaitUninterruptibly()里的中断处理

那么,啥时候需要处理中断?捕获到中断后,又该做些什么呢?
本文就通过await()和awaitUninterruptibly()的代码,来理解一下这里提出的两个问题。

捕获到中断后该做些什么,是要取决于用户具体的需求的。但是,有一个基本的原则是,除非是刻意为之,否则不要将中断随随便便吞掉了。

awaitUninterruptibly()和await()的代码如下:


//没有抛出异常
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true; //记录中断标识
}
//看是否要将中断标识再次设置为true
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}


public final void await() throws InterruptedException {
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进行不同处理
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

比较这两个await接口,你会发现它们都是在LockSupport.park(this)将线程挂起之后,才开始涉及到中断的处理。(这里忽略了await()刚开始的那个中断处理)

awaitUninterruptibly()

仅仅是保留了下中断标识(并不关心true还是false),没有做什么特别的事情,用户基本感知不到啥。

不知为何awaitUninterruptibly()特意处理了一下中断,其实不处理的话,中断标识本来就一直保留着的。

这里yy一下,awaitUninterruptibly()处理中断或许是为了区分开这两种情况:

  1. 自己调用LockSupport.park()导致线程挂起,在挂起期间发生的中断;
  2. acquireQueued()调用LockSupport.park()导致线程挂起,在挂起期间发生的中断。

从代码中可以看到,awaitUninterruptibly()在最后调用了acquireQueued(),acquireQueued()的返回值表示在acquireQueued()处理的过程中是否被中断过。awaitUninterruptibly()在线程挂起恢复之后,清除了当前线程的中断标识,并用一个局部变量interrupted重新记录了中断标识。
这样,acquireQueued()在判断是否有中断的时候,就不会受到之前中断的影响了,而确确实实是在判断acquireQueued()这个方法本身是否有中断发生过。
不过,即使如此,awaitUninterruptibly()中的中断处理看起来仍是无甚鸟用。

await()

await()与awaitUninterruptibly()最明显的区别就是抛出了InterruptedException异常。
要说awaitUninterruptibly()是用户在无需顾及中断的时候使用,那么await()就是在用户想要程序能够及时响应中断时使用。

await()在什么情况下抛出异常
不是有中断就一定会抛出异常,await()在什么情况下才会抛出InterruptedException呢?

await()中有个interruptMode,有三个值:

  1. THROW_IE:抛出异常
  2. REINTERRUPT:重置了中断标识,不抛异常
  3. 0:未发生中断,啥都不干

是THROW_IE还是REINTERRUPT,是由checkInterruptWhileWaiting()返回的。
这个接口的实现很简洁


return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;

注释里给的说明也很清晰:
Checks for interrupt, returning THROW_IE if interrupted before signalled, REINTERRUPT if after signalled, or 0 if not interrupted.

意思就是:

  1. 没有中断,interruptMode = 0
  2. 中断发生在signal之前,interruptMode = THROW_IE
  3. 中断发生在signal之后,interruptMode = REINTERRUPT

为啥扯到了singal

await()和signal()是捉对的,await()挂起线程,signal()则是唤醒线程。

  1. 如果用户已经调用了signal,线程的唤醒是用户期望中的行为,主导权已经回到用户手中。此时,await()就不用急着根据中断来做什么及时响应了。所以,await()顶多是设置一下中断标识。
  2. 如果用户未调用signal,而线程却被唤醒了呢?
    这或许不是用户想要的(线程是因为何种异常,在用户未知的情况下被唤醒了呢?没想出来。。。),这个时候,await()就要抛出异常以通知用户。
    也或许就是用户在已经考虑到的异常中,主动将线程Interrupt了,那么当然,await()也要抛出异常通知用户。

Interrupt: When&How

When

那么,到底什么时候需要处理中断呢?其实,这里应该换一个更确切的说法,那就是什么时候,我们需要中断的支持呢?

其中的一种情况就是程序被长时间挂起的时候。
比如说本文的LockSupoort.park(this),它响应中断,但不抛出异常(两个await方法也是基于该方法实现对中断的响应的)。
再比如说Thread.sleep(),它响应中断,并且抛出异常。
下面举一个实际运用到Thread.sleep()来响应中断的例子。


while(run) {
//do something
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do something
}
//clean
}

比如一个子线程实现了一个类似上面的定时任务,父线程通过设置run为false,来和谐的通知子线程退出。但是假如父线程在设置run为false时,子线程正好在sleep呢?你不想等个10秒该怎么办?你可以用interrupt打断睡眠。
这个interrupt就好像是sleep()中又为你做了另一个小小的定时任务,一个检查是否要中断线程的小小的定时任务(不过实际上,也正是如此吧)。

How

那捕获到中断后要做些什么呢?
这跟用户的需求是相关的。
再重复一下awaitUninterruptibly()和await()。

  1. 用户想在中断后,程序还能够没事一样照常运行,为此实现了awaitUninterruptibly(),它只是默默的传递了下中断标识;
  2. 用户想要针对中断的情况进行特殊的处理,因此await()抛出了中断异常让用户来捕获。

补充

主要内容说完了,还有一个地方想叨逼一下。
await()刚开始的那个中断处理,大约是为了性能考虑,使得该接口能够及时响应中断,尽量避免被挂起。


PS:在琢磨awaitUninterruptibly()为啥特意处理了下中断,并扯到acquireQueued()的时候,我突然想起一件事情。
小时候语文老师在给我们解析课文的时候,总会说,这一句表达了作者的爱国/愤懑/哀思等等等等,在写这一句的时候,作者脑海里是在想着这个那个。
我就嘀咕啊,你特么怎么知道作者在想啥?作者早就驾鹤西去了,死无对证,由的你们去说啊!作者要是活过来,听到你们在这叨逼叨,说不定啼笑皆非。
当我自己在看代码的时候,我发现自己在犯同样的毛病,我也会去琢磨:作者为何要这样写?
虽然说文学更加抽象,但是,各行各业瞎捉摸的心情,估计是一样的吧。

你可能感兴趣的:(Java AbstractQueuedSynchronizer源码阅读5-从await()和awaitUninterruptibly()看interrupt)