Java AbstractQueuedSynchronizer源碼閱讀5-從await()和awaitUninterruptibly()看interrupt

這篇文章說(shuō)是對(duì)AbstractQueuedSynchronizer源碼的閱讀,倒不如說(shuō)是對(duì)java interrupt的理解。

在看await()和awaitInterruptibly()的代碼前,我們先來(lái)了解下java的中斷機(jī)制。

java中斷機(jī)制

本文對(duì)中斷機(jī)制的理解參考了這篇文章詳細(xì)分析Java中斷機(jī)制。

  1. 為啥有Interrupt這個(gè)東西?
    因?yàn)榇嬖谶@么個(gè)需求:一個(gè)線程去中斷另一個(gè)線程。

  2. Interrupt是如何工作的?
    每個(gè)線程有一個(gè)中斷標(biāo)識(shí),想要中斷這個(gè)線程,可以將該線程的中斷標(biāo)識(shí)設(shè)置為true。該線程會(huì)在適當(dāng)時(shí)機(jī)檢查自己的中斷標(biāo)識(shí),并決定如何處理中斷。
    這種中斷不具有強(qiáng)制性,應(yīng)該屬于軟中斷?

java里和中斷有關(guān)的三個(gè)方法如下:

方法 作用
boolean interrupted() 測(cè)試當(dāng)前線程是否已經(jīng)中斷。線程的中斷狀態(tài)由該方法清除。換句話說(shuō),如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線程再次中斷的情況除外)
boolean isInterrupted() 測(cè)試線程是否已經(jīng)中斷。線程的中斷狀態(tài)不受該方法的影響
void interrupt() 中斷線程

await()和awaitUninterruptibly()里的中斷處理

那么,啥時(shí)候需要處理中斷?捕獲到中斷后,又該做些什么呢?
本文就通過(guò)await()和awaitUninterruptibly()的代碼,來(lái)理解一下這里提出的兩個(gè)問(wèn)題。

捕獲到中斷后該做些什么,是要取決于用戶(hù)具體的需求的。但是,有一個(gè)基本的原則是,除非是刻意為之,否則不要將中斷隨隨便便吞掉了。

awaitUninterruptibly()和await()的代碼如下:
<pre>
//沒(méi)有拋出異常
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; //記錄中斷標(biāo)識(shí)
}
//看是否要將中斷標(biāo)識(shí)再次設(shè)置為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);
//設(shè)置InterruptMode
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//根據(jù)InterruptMode進(jìn)行不同處理
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
</pre>

比較這兩個(gè)await接口,你會(huì)發(fā)現(xiàn)它們都是在LockSupport.park(this)將線程掛起之后,才開(kāi)始涉及到中斷的處理。(這里忽略了await()剛開(kāi)始的那個(gè)中斷處理)

awaitUninterruptibly()

僅僅是保留了下中斷標(biāo)識(shí)(并不關(guān)心true還是false),沒(méi)有做什么特別的事情,用戶(hù)基本感知不到啥。

不知為何awaitUninterruptibly()特意處理了一下中斷,其實(shí)不處理的話,中斷標(biāo)識(shí)本來(lái)就一直保留著的。

這里yy一下,awaitUninterruptibly()處理中斷或許是為了區(qū)分開(kāi)這兩種情況:

  1. 自己調(diào)用LockSupport.park()導(dǎo)致線程掛起,在掛起期間發(fā)生的中斷;
  2. acquireQueued()調(diào)用LockSupport.park()導(dǎo)致線程掛起,在掛起期間發(fā)生的中斷。

從代碼中可以看到,awaitUninterruptibly()在最后調(diào)用了acquireQueued(),acquireQueued()的返回值表示在acquireQueued()處理的過(guò)程中是否被中斷過(guò)。awaitUninterruptibly()在線程掛起恢復(fù)之后,清除了當(dāng)前線程的中斷標(biāo)識(shí),并用一個(gè)局部變量interrupted重新記錄了中斷標(biāo)識(shí)。
這樣,acquireQueued()在判斷是否有中斷的時(shí)候,就不會(huì)受到之前中斷的影響了,而確確實(shí)實(shí)是在判斷acquireQueued()這個(gè)方法本身是否有中斷發(fā)生過(guò)。
不過(guò),即使如此,awaitUninterruptibly()中的中斷處理看起來(lái)仍是無(wú)甚鳥(niǎo)用。

await()

await()與awaitUninterruptibly()最明顯的區(qū)別就是拋出了InterruptedException異常。
要說(shuō)awaitUninterruptibly()是用戶(hù)在無(wú)需顧及中斷的時(shí)候使用,那么await()就是在用戶(hù)想要程序能夠及時(shí)響應(yīng)中斷時(shí)使用。

await()在什么情況下拋出異常
不是有中斷就一定會(huì)拋出異常,await()在什么情況下才會(huì)拋出InterruptedException呢?

await()中有個(gè)interruptMode,有三個(gè)值:

  1. THROW_IE:拋出異常
  2. REINTERRUPT:重置了中斷標(biāo)識(shí),不拋異常
  3. 0:未發(fā)生中斷,啥都不干

是THROW_IE還是REINTERRUPT,是由checkInterruptWhileWaiting()返回的。
這個(gè)接口的實(shí)現(xiàn)很簡(jiǎn)潔
<pre>
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
</pre>
注釋里給的說(shuō)明也很清晰:
Checks for interrupt, returning THROW_IE if interrupted before signalled, REINTERRUPT if after signalled, or 0 if not interrupted.

意思就是:

  1. 沒(méi)有中斷,interruptMode = 0
  2. 中斷發(fā)生在signal之前,interruptMode = THROW_IE
  3. 中斷發(fā)生在signal之后,interruptMode = REINTERRUPT

為啥扯到了singal

await()和signal()是捉對(duì)的,await()掛起線程,signal()則是喚醒線程。

  1. 如果用戶(hù)已經(jīng)調(diào)用了signal,線程的喚醒是用戶(hù)期望中的行為,主導(dǎo)權(quán)已經(jīng)回到用戶(hù)手中。此時(shí),await()就不用急著根據(jù)中斷來(lái)做什么及時(shí)響應(yīng)了。所以,await()頂多是設(shè)置一下中斷標(biāo)識(shí)。
  2. 如果用戶(hù)未調(diào)用signal,而線程卻被喚醒了呢?
    這或許不是用戶(hù)想要的(線程是因?yàn)楹畏N異常,在用戶(hù)未知的情況下被喚醒了呢?沒(méi)想出來(lái)。。。),這個(gè)時(shí)候,await()就要拋出異常以通知用戶(hù)。
    也或許就是用戶(hù)在已經(jīng)考慮到的異常中,主動(dòng)將線程Interrupt了,那么當(dāng)然,await()也要拋出異常通知用戶(hù)。

Interrupt: When&How

When

那么,到底什么時(shí)候需要處理中斷呢?其實(shí),這里應(yīng)該換一個(gè)更確切的說(shuō)法,那就是什么時(shí)候,我們需要中斷的支持呢?

其中的一種情況就是程序被長(zhǎng)時(shí)間掛起的時(shí)候。
比如說(shuō)本文的LockSupoort.park(this),它響應(yīng)中斷,但不拋出異常(兩個(gè)await方法也是基于該方法實(shí)現(xiàn)對(duì)中斷的響應(yīng)的)。
再比如說(shuō)Thread.sleep(),它響應(yīng)中斷,并且拋出異常。
下面舉一個(gè)實(shí)際運(yùn)用到Thread.sleep()來(lái)響應(yīng)中斷的例子。
<pre>
while(run) {
//do something
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do something
}
//clean
}
</pre>
比如一個(gè)子線程實(shí)現(xiàn)了一個(gè)類(lèi)似上面的定時(shí)任務(wù),父線程通過(guò)設(shè)置run為false,來(lái)和諧的通知子線程退出。但是假如父線程在設(shè)置run為false時(shí),子線程正好在sleep呢?你不想等個(gè)10秒該怎么辦?你可以用interrupt打斷睡眠。
這個(gè)interrupt就好像是sleep()中又為你做了另一個(gè)小小的定時(shí)任務(wù),一個(gè)檢查是否要中斷線程的小小的定時(shí)任務(wù)(不過(guò)實(shí)際上,也正是如此吧)。

How

那捕獲到中斷后要做些什么呢?
這跟用戶(hù)的需求是相關(guān)的。
再重復(fù)一下awaitUninterruptibly()和await()。

  1. 用戶(hù)想在中斷后,程序還能夠沒(méi)事一樣照常運(yùn)行,為此實(shí)現(xiàn)了awaitUninterruptibly(),它只是默默的傳遞了下中斷標(biāo)識(shí);
  2. 用戶(hù)想要針對(duì)中斷的情況進(jìn)行特殊的處理,因此await()拋出了中斷異常讓用戶(hù)來(lái)捕獲。

補(bǔ)充

主要內(nèi)容說(shuō)完了,還有一個(gè)地方想叨逼一下。
await()剛開(kāi)始的那個(gè)中斷處理,大約是為了性能考慮,使得該接口能夠及時(shí)響應(yīng)中斷,盡量避免被掛起。


PS:在琢磨awaitUninterruptibly()為啥特意處理了下中斷,并扯到acquireQueued()的時(shí)候,我突然想起一件事情。
小時(shí)候語(yǔ)文老師在給我們解析課文的時(shí)候,總會(huì)說(shuō),這一句表達(dá)了作者的愛(ài)國(guó)/憤懣/哀思等等等等,在寫(xiě)這一句的時(shí)候,作者腦海里是在想著這個(gè)那個(gè)。
我就嘀咕啊,你特么怎么知道作者在想啥?作者早就駕鶴西去了,死無(wú)對(duì)證,由的你們?nèi)フf(shuō)??!作者要是活過(guò)來(lái),聽(tīng)到你們?cè)谶@叨逼叨,說(shuō)不定啼笑皆非。
當(dāng)我自己在看代碼的時(shí)候,我發(fā)現(xiàn)自己在犯同樣的毛病,我也會(huì)去琢磨:作者為何要這樣寫(xiě)?
雖然說(shuō)文學(xué)更加抽象,但是,各行各業(yè)瞎捉摸的心情,估計(jì)是一樣的吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容