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()的代码如下:
<pre>
//没有抛出异常
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();
}
</pre>
<pre>
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);
}
</pre>

比较这两个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()返回的。
这个接口的实现很简洁
<pre>
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
</pre>
注释里给的说明也很清晰:
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()来响应中断的例子。
<pre>
while(run) {
//do something
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do something
}
//clean
}
</pre>
比如一个子线程实现了一个类似上面的定时任务,父线程通过设置run为false,来和谐的通知子线程退出。但是假如父线程在设置run为false时,子线程正好在sleep呢?你不想等个10秒该怎么办?你可以用interrupt打断睡眠。
这个interrupt就好像是sleep()中又为你做了另一个小小的定时任务,一个检查是否要中断线程的小小的定时任务(不过实际上,也正是如此吧)。

How

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

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

补充

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容