朴實的聊聊很多人會誤解/不懂的Java並發中斷機制


| 好看請贊,養成習慣

  • 你有一個思想,我有一個思想,我們交換后,一個人就有兩個思想

  • 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()

該方法注釋上寫的很清楚,當線程被阻塞在:

  1. wait()
  2. join()
  3. 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,不保存數據直接中斷好嗎?);
  • 某個操作超過了一定的執行時間限制需要中止時;
  • 多個線程做相同的事情,只要一個線程成功其它線程都可以取消時;
  • 一組線程中的一個或多個出現錯誤導致整組都無法繼續時;

因為中斷是一種協同機制,提供了更優雅中斷方式,也提供了更多的靈活性,所以當遇到如上場景等,我們就可以考慮使用中斷機制了

使用中斷機制有哪些注意事項

其實使用中斷機制無非就是注意上面說的兩項內容:

  1. 中斷標識
  2. 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源碼一些常見案例,相信你已經胸中有溝壑了,接下來,跟上節奏,我們陸續走進源碼吧

靈魂追問

  1. 拋出 InterruptedException 后,中斷標識就一定被清空嗎?
  2. 處在死鎖狀態的線程是否可以被中斷呢?
  3. 進入臨界區的線程能否被中斷呢?如果不能有什么辦法能響應中斷嗎?
  4. 個人感覺interrupted這個方法名稱不是特別好,如果你也覺得不好,讓你設計這個地方,你有什么想法?

有朋友可能會問文章開頭的圖,同時看一個類的不同部分怎么實現的?不等您開口,我就全盤的招了,其實就是屏幕分割(在文件上鼠標右鍵->選擇水平/垂直分割),這樣在同時查看某些代碼時還是很方便的(帶魚屏垂直分割真是爽翻天),保姆式演示如下:

參考

  1. Java 並發編程實戰
  2. Java並發編程的藝術
  3. https://www.infoq.cn/article/java-interrupt-mechanism
  4. https://coderanch.com/t/237332/certification/explain-interrupt-isInterrupted-interrupted-method
  5. https://dzone.com/articles/waiting-for-coroutines


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM