聊聊貫穿Java并發(fā)編程的中斷機(jī)制
本來(lái)是要介紹 AQS 作為我們走進(jìn)并發(fā)編程源碼環(huán)節(jié)的第一步,但 AQS 涉及的知識(shí)點(diǎn)也還真有點(diǎn)多,每一個(gè)都?jí)騿为?dú)拿出來(lái)說(shuō)一說(shuō),恰巧有朋友私信我“不理解線程的中斷機(jī)制”,中斷機(jī)制又恰巧是 AQS API實(shí)現(xiàn)的一部分,更貫穿于整個(gè)并發(fā)編程內(nèi)容中。于是就打算單獨(dú)說(shuō)一說(shuō)這個(gè)小機(jī)制,先讓大家做到心中有 number
在學(xué)習(xí)/編寫(xiě)并發(fā)程序時(shí),總會(huì)聽(tīng)到/看到如下詞匯:
- 線程被中斷或拋出InterruptedException
- 設(shè)置了中斷標(biāo)識(shí)
- 清空了中斷標(biāo)識(shí)
- 判斷線程是否被中斷
在 Java Thread 類(lèi)又提供了長(zhǎng)相酷似,讓人傻傻分不清的三個(gè)方法來(lái)處理并發(fā)中斷問(wèn)題:
interrupt()interrupted()isInterrupted()

看到這我不禁會(huì)問(wèn)自己:
什么是中斷機(jī)制?

剛剛接觸【中斷】這個(gè)詞時(shí),先入為主的概念就是“直接中斷/打斷”正在做的事,使其停止。我的理解是這樣的:
你:在打游戲
女朋友:別打游戲了,趕快過(guò)來(lái)吃飯
你:聽(tīng)到女朋友招呼之后立馬中斷手中的游戲乖乖過(guò)去吃飯

在多線程編程中,中斷是一種【協(xié)同】機(jī)制,怎么理解這么高大上的詞呢?就是女朋友叫你吃飯,你收到了中斷游戲通知,但是否馬上放下手中的游戲去吃飯看你心情 。在程序中怎樣演繹這個(gè)心情就看具體的業(yè)務(wù)邏輯了,Java 的中斷機(jī)制就是這么簡(jiǎn)單
如果還沒(méi)改變這個(gè)先入為主的概念,我懷你沒(méi)有女朋友(?)我們擁抱一下
為什么會(huì)有中斷機(jī)制?
中斷是一種協(xié)同機(jī)制,我覺(jué)得就是解決【當(dāng)局者迷】的狀況
現(xiàn)實(shí)中,你努力忘我沒(méi)有晝夜的工作,如果再?zèng)]有人告知你中斷,你身體是吃不消的。
在多線程的場(chǎng)景中,有的線程可能迷失在怪圈無(wú)法自拔(自旋浪費(fèi)資源),這時(shí)就可以用其他線程在恰當(dāng)的時(shí)機(jī)給它個(gè)中斷通知,被“中斷”的線程可以選擇在恰當(dāng)的時(shí)機(jī)選擇跳出怪圈,最大化的利用資源
那程序中如何中斷?怎樣識(shí)別是否中斷?又如何處理中斷呢?這就與上文提到的三個(gè)方法有關(guān)了
interrupt() VS isInterrupted() VS interrupted()
Java 的每個(gè)線程對(duì)象里都有一個(gè) boolean 類(lèi)型的標(biāo)識(shí),代表是否有中斷請(qǐng)求,可你尋遍 Thread 類(lèi)你也不會(huì)找到這個(gè)標(biāo)識(shí),因?yàn)檫@是通過(guò)底層 native 方法實(shí)現(xiàn)的。
interrupt()
interrupt() 方法是 唯一一個(gè) 可以將上面提到中斷標(biāo)志設(shè)置為 true 的方法,從這里可以看出,這是一個(gè) Thread 類(lèi) public 的對(duì)象方法,所以可以推斷出任何線程對(duì)象都可以調(diào)用該方法,進(jìn)一步說(shuō)明就是可以一個(gè)線程 interrupt 其他線程,也可以 interrupt 自己。其中,中斷標(biāo)識(shí)的設(shè)置是通過(guò) native 方法 interrupt0 完成的

在 Java 中,線程被中斷的反應(yīng)是不一樣的,脾氣不好的直接就拋出了 InterruptedException() ,

該方法注釋上寫(xiě)的很清楚,當(dāng)線程被阻塞在:
- wait()
- join()
- sleep()
這些方法時(shí),如果被中斷,就會(huì)拋出 InterruptedException 受檢異常(也就是必須要求我們 catch 進(jìn)行處理的)
熟悉 JUC 的朋友可能知道,其實(shí)被中斷拋出 InterruptedException 的遠(yuǎn)遠(yuǎn)不止這幾個(gè)方法,比如:

反向推理,這些可能阻塞的方法如果聲明有 throws InterruptedException , 也就暗示我們它們是可中斷的
調(diào)用 interrput() 方法后,中斷標(biāo)識(shí)就被設(shè)置為 true 了,那我們?cè)趺蠢眠@個(gè)中斷標(biāo)識(shí),來(lái)判斷某個(gè)線程中斷標(biāo)識(shí)到底什么狀態(tài)呢?
isInterrupted()

這個(gè)方法名起的非常好,因?yàn)楸容^符合我們 bean boolean 類(lèi)型字段的 get 方法規(guī)范,沒(méi)錯(cuò),該方法就是返回中斷標(biāo)識(shí)的結(jié)果:
- true:線程被中斷,
- false:線程沒(méi)被中斷或被清空了中斷標(biāo)識(shí)(如何清空我們一會(huì)兒看)
拿到這個(gè)標(biāo)識(shí)后,線程就可以判斷這個(gè)標(biāo)識(shí)來(lái)執(zhí)行后續(xù)的邏輯了。有起名好的,也有起名不好的,就是下面這個(gè)方法:
interrupted()
按照常規(guī)翻譯,過(guò)去時(shí)時(shí)態(tài),這就是“被打斷了/被打斷的”,其實(shí)和上面的 isInterrupted() 方法差不多,兩個(gè)方法都是調(diào)用 private 的 isInterrupted() 方法, 唯一差別就是會(huì)清空中斷標(biāo)識(shí)(這是從方法名中怎么也看不出來(lái)的)

因?yàn)檎{(diào)用該方法,會(huì)返回當(dāng)前中斷標(biāo)識(shí),同時(shí)會(huì)清空中斷標(biāo)識(shí),就有了那一段有點(diǎn)讓人迷惑的方法注釋?zhuān)?/p>

來(lái)段程序你就會(huì)明白上面注釋的意思了:
Thread.currentThread().isInterrupted(); // true
Thread.interrupted() // true,返回true后清空了中斷標(biāo)識(shí)將其置為 false
Thread.currentThread().isInterrupted(); // false
Thread.interrupted() // false
這個(gè)方法總覺(jué)得很奇怪,現(xiàn)實(shí)中有什么用呢?
當(dāng)你可能要被大量中斷并且你想確保只處理一次中斷時(shí),就可以使用這個(gè)方法了
該方法在 JDK 源碼中應(yīng)用也非常多,比如(后續(xù)文章會(huì)具體分析,這里知道該方法的作用和使用場(chǎng)景就好):

相信到這里你已經(jīng)能明確分辨三胞胎都是誰(shuí),并發(fā)揮怎樣的作用了,那么有哪些場(chǎng)景我們可以使用中斷機(jī)制呢?
中斷機(jī)制的使用場(chǎng)景
通常,中斷的使用場(chǎng)景有以下幾個(gè)
- 點(diǎn)擊某個(gè)桌面應(yīng)用中的關(guān)閉按鈕時(shí)(比如你關(guān)閉 IDEA,不保存數(shù)據(jù)直接中斷好嗎?);
- 某個(gè)操作超過(guò)了一定的執(zhí)行時(shí)間限制需要中止時(shí);
- 多個(gè)線程做相同的事情,只要一個(gè)線程成功其它線程都可以取消時(shí);
- 一組線程中的一個(gè)或多個(gè)出現(xiàn)錯(cuò)誤導(dǎo)致整組都無(wú)法繼續(xù)時(shí);
因?yàn)橹袛嗍且环N協(xié)同機(jī)制,提供了更優(yōu)雅中斷方式,也提供了更多的靈活性,所以當(dāng)遇到如上場(chǎng)景等,我們就可以考慮使用中斷機(jī)制了
使用中斷機(jī)制有哪些注意事項(xiàng)
其實(shí)使用中斷機(jī)制無(wú)非就是注意上面說(shuō)的兩項(xiàng)內(nèi)容:
- 中斷標(biāo)識(shí)
- InterruptedException
前浪已經(jīng)將其總結(jié)為兩個(gè)通用原則,我們后浪直接站在肩膀上用就可以了,來(lái)看一下這兩個(gè)原則是什么:
原則-1
如果遇到的是可中斷的阻塞方法, 并拋出 InterruptedException,可以繼續(xù)向方法調(diào)用棧的上層拋出該異常;如果檢測(cè)到中斷,則可清除中斷狀態(tài)并拋出 InterruptedException,使當(dāng)前方法也成為一個(gè)可中斷的方法
原則-2
若有時(shí)候不太方便在方法上拋出 InterruptedException,比如要實(shí)現(xiàn)的某個(gè)接口中的方法簽名上沒(méi)有 throws InterruptedException,這時(shí)就可以捕獲可中斷方法的 InterruptedException 并通過(guò) Thread.currentThread.interrupt() 來(lái)重新設(shè)置中斷狀態(tài)。
再通過(guò)個(gè)例子來(lái)加深一下理解:
本意是當(dāng)前線程被中斷之后,退出while(true), 你覺(jué)得代碼有問(wèn)題嗎?(先不要向下看)
Thread th = Thread.currentThread();
while(true) {
if(th.isInterrupted()) {
break;
}
// 省略業(yè)務(wù)代碼
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
打開(kāi) Thread.sleep 方法:

sleep 方法拋出 InterruptedException后,中斷標(biāo)識(shí)也被清空置為 false,我們?cè)赾atch 沒(méi)有通過(guò)調(diào)用 th.interrupt() 方法再次將中斷標(biāo)識(shí)置為 true,這就導(dǎo)致無(wú)限循環(huán)了
這兩個(gè)原則很好理解。總的來(lái)說(shuō),我們應(yīng)該留意 InterruptedException,當(dāng)我們捕獲到該異常時(shí),絕不可以默默的吞掉它,什么也不做,因?yàn)檫@會(huì)導(dǎo)致上層調(diào)用棧什么信息也獲取不到。其實(shí)在編寫(xiě)程序時(shí),捕獲的任何受檢異常我們都不應(yīng)該吞掉
JDK 中有哪些使用中斷機(jī)制的地方呢?
中斷機(jī)制貫穿整個(gè)并發(fā)編程中,這里只簡(jiǎn)單列覺(jué)大家經(jīng)常會(huì)使用的,我們可以通過(guò)閱讀JDK源碼來(lái)進(jìn)一步了解中斷機(jī)制以及學(xué)習(xí)如何使用中斷機(jī)制
ThreadPoolExecutor
ThreadPoolExecutor 中的 shutdownNow 方法會(huì)遍歷線程池中的工作線程并調(diào)用線程的 interrupt 方法來(lái)中斷線程


FutureTask
FutureTask 中的 cancel 方法,如果傳入的參數(shù)為 true,它將會(huì)在正在運(yùn)行異步任務(wù)的線程上調(diào)用 interrupt 方法,如果正在執(zhí)行的異步任務(wù)中的代碼沒(méi)有對(duì)中斷做出響應(yīng),那么 cancel 方法中的參數(shù)將不會(huì)起到什么效果

總結(jié)
到這里你應(yīng)該理解Java 并發(fā)編程中斷機(jī)制的含義了,它是一種協(xié)同機(jī)制,和你先入為主的概念完全不一樣。區(qū)分了三個(gè)相近方法,說(shuō)明了使用場(chǎng)景以及使用原則,同時(shí)又給出JDK源碼一些常見(jiàn)案例,相信你已經(jīng)胸中有溝壑了,接下來(lái),跟上節(jié)奏,我們陸續(xù)走進(jìn)源碼吧
靈魂追問(wèn)
- 拋出 InterruptedException 后,中斷標(biāo)識(shí)就一定被清空嗎?
- 處在死鎖狀態(tài)的線程是否可以被中斷呢?
- 進(jìn)入臨界區(qū)的線程能否被中斷呢?如果不能有什么辦法能響應(yīng)中斷嗎?
- 個(gè)人感覺(jué)interrupted這個(gè)方法名稱(chēng)不是特別好,如果你也覺(jué)得不好,讓你設(shè)計(jì)這個(gè)地方,你有什么想法?
有朋友可能會(huì)問(wèn)文章開(kāi)頭的圖,同時(shí)看一個(gè)類(lèi)的不同部分怎么實(shí)現(xiàn)的?不等您開(kāi)口,我就全盤(pán)招了,其實(shí)就是屏幕分割(在文件上鼠標(biāo)右鍵->選擇水平/垂直分割),這樣在同時(shí)查看某些代碼時(shí)還是很方便的(帶魚(yú)屏垂直分割真是爽翻天),保姆式演示如下(由于公眾號(hào)限制,完整動(dòng)圖查看原文吧):

參考
- Java 并發(fā)編程實(shí)戰(zhàn)
- Java并發(fā)編程的藝術(shù)
- https://www.infoq.cn/article/java-interrupt-mechanism
- https://coderanch.com/t/237332/certification/explain-interrupt-isInterrupted-interrupted-method
- https://dzone.com/articles/waiting-for-coroutines
文末福利:我總結(jié)了一套 5000 頁(yè)的 Java 學(xué)習(xí)手冊(cè),在知乎已經(jīng)3萬(wàn)贊了!此手冊(cè)內(nèi)容專(zhuān)注 Java技術(shù),包括 JavaWeb,SSM,Linux,Spring Boot,MyBatis,MySQL,Nginx,Git,GitHub,Servlet,IDEA,多線程,集合,JVM,DeBug, Dubbo,Redis,算法,面試題等相關(guān)內(nèi)容。
下載方式
1. 首先掃描下方二維碼
2. 后臺(tái)回復(fù)「555」即可獲取
注明:僅僅作為知識(shí)分享,切勿用于其它商業(yè)活動(dòng) 。感謝所有技術(shù)分享者的付出。
點(diǎn)贊是最大的支持 ![]()
