| 好看請贊,養成習慣
你有一個思想,我有一個思想,我們交換后,一個人就有兩個思想
If you can NOT explain it simply, you do NOT understand it well enough
現陸續將Demo代碼和技術文章整理在一起 Github實踐精選 ,方便大家閱讀查看,本文同樣收錄在此,覺得不錯,還請Star??
橫看成嶺側成峰,遠近高低各不同,并發編程理論系列基本已經結束,相信大家有了理論的鋪墊,近看原始碼才能發現其設計之美,不會一頭霧水
本來是要介紹 AQS 作為我們走進并發編程原始碼環節的第一步,但 AQS 涉及的知識點也還真有點多,每一個都夠單獨拿出來說一說,恰巧有朋友私信我“不理解執行緒的中斷機制”,中斷機制又恰巧是 AQS API實作的一部分,更貫穿于整個并發編程內容中,于是就打算單獨說一說這個小機制,先讓大家做到心中有 number
在學習/撰寫并發程式時,總會聽到/看到如下詞匯:
- 執行緒被中斷或拋出InterruptedException
- 設定了中斷標識
- 清空了中斷標識
- 判斷執行緒是否被中斷
在 Java Thread 類又提供了長相酷似,讓人傻傻分不清的三個方法來處理并發中斷問題:
interrupt()interrupted()isInterrupted()

看到這我不禁會問自己:

什么是中斷機制?

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

在多執行緒編程中,中斷是一種【協同】機制,怎么理解這么高大上的詞呢?就是女朋友叫你吃飯,你收到了中斷游戲通知,但是否馬上放下手中的游戲去吃飯看你心情 ,在程式中怎樣演繹這個心情就看具體的業務邏輯了,Java 的中斷機制就是這么簡單
如果還沒改變這個先入為主的概念,我懷你你沒有女朋友(??)我們擁抱一下
為什么會有中斷機制?
中斷是一種協同機制,我覺得就是解決【當局者迷】的狀況
現實中,你努力忘我沒有晝夜的作業,如果再沒有人告知你中斷,你身體是吃不消的,
在多執行緒的場景中,有的執行緒可能迷失在怪圈無法自拔(自旋浪費資源),這時就可以用其他執行緒在恰當的時機給它個中斷通知,被“中斷”的執行緒可以選擇在恰當的時機選擇跳出怪圈,最大化的利用資源
那程式中如何中斷?怎樣識別是否中斷?又如何處理中斷呢?這就與上文提到的三個方法有關了
interrupt() VS isInterrupted() VS interrupted()
Java 的每個執行緒物件里都有一個 boolean 型別的標識,代表是否有中斷請求,可你尋遍 Thread 類你也不會找到這個標識,因為這是通過底層 native 方法實作的,
interrupt()
interrupt() 方法是 唯一一個 可以將上面提到中斷標志設定為 true 的方法,從這里可以看出,這是一個 Thread 類 public 的物件方法,所以可以推斷出任何執行緒物件都可以呼叫該方法,進一步說明就是可以一個執行緒 interrupt 其他執行緒,也可以 interrupt 自己,其中,中斷標識的設定是通過 native 方法 interrupt0 完成的

在 Java 中,執行緒被中斷的反應是不一樣的,脾氣不好的直接就拋出了 InterruptedException() ,

該方法注釋上寫的很清楚,當執行緒被阻塞在:
- wait()
- join()
- sleep()
這些方法時,如果被中斷,就會拋出 InterruptedException 受檢例外(也就是必須要求我們 catch 進行處理的)
熟悉 JUC 的朋友可能知道,其實被中斷拋出 InterruptedException 的遠遠不止這幾個方法,比如:

反向推理,這些可能阻塞的方法如果宣告有 throws InterruptedException , 也就暗示我們它們是可中斷的
呼叫 interrput() 方法后,中斷標識就被設定為 true 了,那我們怎么利用這個中斷標識,來判斷某個執行緒中斷標識到底什么狀態呢?
isInterrupted()

這個方法名起的非常好,因為比較符合我們 bean boolean 型別欄位的 get 方法規范,沒錯,該方法就是回傳中斷標識的結果:
- true:執行緒被中斷,
- false:執行緒沒被中斷或被清空了中斷標識(如何清空我們一會看)
拿到這個標識后,執行緒就可以判斷這個標識來執行后續的邏輯了,有起名好的,也有起名不好的,就是下面這個方法:
interrupted()
按照常規翻譯,過去時時態,這就是“被打斷了/被打斷的”,其實和上面的 isInterrupted() 方法差不多,兩個方法都是呼叫 private 的 isInterrupted() 方法, 唯一差別就是會清空中斷標識(這是從方法名中怎么也看不出來的)

因為呼叫該方法,會回傳當前中斷標識,同時會清空中斷標識,就有了那一段有點讓人迷惑的方法注釋:

來段程式你就會明白上面注釋的意思了:
Thread.currentThread().isInterrupted(); // true
Thread.interrupted() // true,回傳true后清空了中斷標識將其置為 false
Thread.currentThread().isInterrupted(); // false
Thread.interrupted() // false
這個方法總覺得很奇怪,現實中有什么用呢?
當你可能要被大量中斷并且你想確保只處理一次中斷時,就可以使用這個方法了
該方法在 JDK 原始碼中應用也非常多,比如(后續文章會具體分析,這里知道該方法的作用和使用場景就好):

相信到這里你已經能明確分辨三胞胎都是誰,并發揮怎樣的作用了,那么有哪些場景我們可以使用中斷機制呢?
中斷機制的使用場景
通常,中斷的使用場景有以下幾個
- 點擊某個桌面應用中的關閉按鈕時(比如你關閉 IDEA,不保存資料直接中斷好嗎?);
- 某個操作超過了一定的執行時間限制需要中止時;
- 多個執行緒做相同的事情,只要一個執行緒成功其它執行緒都可以取消時;
- 一組執行緒中的一個或多個出現錯誤導致整組都無法繼續時;
因為中斷是一種協同機制,提供了更優雅中斷方式,也提供了更多的靈活性,所以當遇到如上場景等,我們就可以考慮使用中斷機制了
使用中斷機制有哪些注意事項
其實使用中斷機制無非就是注意上面說的兩項內容:
- 中斷標識
- InterruptedException
前浪已經將其總結為兩個通用原則,我們后浪直接站在肩膀上用就可以了,來看一下這兩個原則是什么:
原則-1
如果遇到的是可中斷的阻塞方法, 并拋出 InterruptedException,可以繼續向方法呼叫堆疊的上層拋出該例外;如果檢測到中斷,則可清除中斷狀態并拋出 InterruptedException,使當前方法也成為一個可中斷的方法
原則-2
若有時候不太方便在方法上拋出 InterruptedException,比如要實作的某個介面中的方法簽名上沒有 throws InterruptedException,這時就可以捕獲可中斷方法的 InterruptedException 并通過 Thread.currentThread.interrupt() 來重新設定中斷狀態,
再通過個例子來加深一下理解:
本意是當前執行緒被中斷之后,退出while(true), 你覺得代碼有問題嗎?(先不要向下看)
Thread th = Thread.currentThread();
while(true) {
if(th.isInterrupted()) {
break;
}
// 省略業務代碼
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
打開 Thread.sleep 方法:

sleep 方法拋出 InterruptedException后,中斷標識也被清空置為 false,我們在catch 沒有通過呼叫 th.interrupt() 方法再次將中斷標識置為 true,這就導致無限回圈了
這兩個原則很好理解,總的來說,我們應該留意 InterruptedException,當我們捕獲到該例外時,絕不可以默默的吞掉它,什么也不做,因為這會導致上層呼叫堆疊什么資訊也獲取不到,其實在撰寫程式時,捕獲的任何受檢例外我們都不應該吞掉
JDK 中有哪些使用中斷機制的地方呢?
中斷機制貫穿整個并發編程中,這里只簡單列覺大家經常會使用的,我們可以通過閱讀JDK原始碼來進一步了解中斷機制以及學習如何使用中斷機制
ThreadPoolExecutor
ThreadPoolExecutor 中的 shutdownNow 方法會遍歷執行緒池中的作業執行緒并呼叫執行緒的 interrupt 方法來中斷執行緒


FutureTask
FutureTask 中的 cancel 方法,如果傳入的引數為 true,它將會在正在運行異步任務的執行緒上呼叫 interrupt 方法,如果正在執行的異步任務中的代碼沒有對中斷做出回應,那么 cancel 方法中的引數將不會起到什么效果

總結
到這里你應該理解Java 并發編程中斷機制的含義了,它是一種協同機制,和你先入為主的概念完全不一樣,區分了三個相近方法,說明了使用場景以及使用原則,同時又給出JDK原始碼一些常見案例,相信你已經胸中有溝壑了,接下來,跟上節奏,我們陸續走進原始碼吧
靈魂追問
- 拋出 InterruptedException 后,中斷標識就一定被清空嗎?
- 處在死鎖狀態的執行緒是否可以被中斷呢?
- 進入臨界區的執行緒能否被中斷呢?如果不能有什么辦法能回應中斷嗎?
- 個人感覺interrupted這個方法名稱不是特別好,如果你也覺得不好,讓你設計這個地方,你有什么想法?
有朋友可能會問文章開頭的圖,同時看一個類的不同部分怎么實作的?不等您開口,我就全盤的招了,其實就是螢屏分割(在檔案上滑鼠右鍵->選擇水平/垂直分割),這樣在同時查看某些代碼時還是很方便的(帶魚屏垂直分割真是爽翻天),保姆式演示如下:

參考
- Java 并發編程實戰
- Java并發編程的藝術
- 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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/187923.html
標籤:Java
