協作,才能更好的中斷執行緒

  • 2019 年 10 月 9 日
  • 筆記

聊起中斷,大家可能最熟悉的例子就是執行緒休眠。下面就是一個執行緒休眠的 demo,在這個例子中,當我們調用 sleep 方法,該方法將會拋出一個需要捕獲的中斷異常,這裡捕獲該異常並直接返回。

        for (int i = 0; i < somethings.size(); i++) {              // 休眠 4 s              try {                  Thread.sleep(4000);              } catch (InterruptedException e) {                  // 拋出中斷異常                  return;              }              // 輸出              System.out.println(somethings.get(i));          }

除了 InterruptedException 中斷異常,另外還有三個中斷相關的方法,三個方法都與執行緒相關。

  • thread#interrupt
  • Thread#interrupted
  • thread#isInterrupted

interrupt 方法用於中斷執行緒,但是並不是說該方法就能直接使執行緒停止

下面使用 interrupt 中斷執行緒,這裡我們期望中斷直接停止子執行緒輸出。但是當主執行緒調用子執行緒 interrupt 方法,子執行緒並卻沒有被終止,還在繼續列印數字。

        Runnable interruptedTask=new Runnable() {              @Override              public void run() {                  for (int i = 0; i <Integer.MAX_VALUE ; i++) {                      System.out.println(i);                  }              }          };            Thread interruptThread=new Thread(interruptedTask);          interruptThread.start();          // 休眠 5 s,          TimeUnit.SECONDS.sleep(2);          // 中斷當前執行緒          interruptThread.interrupt();          // 再次休眠,觀察子執行緒          TimeUnit.SECONDS.sleep(2);

引用 Java 官方對於中斷的解釋:

An interrupt is an indication to a thread that it should stop what it is doing and do something else. It’s up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate

中斷僅僅只是表明這個執行緒可以停止,但是執行緒是否停止完全取決於執行緒自己。只有執行緒相互協作,才能更好的停止執行緒。

每個執行緒都包含一個內部標誌,用來表示中斷狀態。調用執行緒的 interrupt 方法將會設置該狀態位,對於 Thread#sleep 等阻塞方法,將會拋出 InterruptedException ,並清除中斷標誌。

我們可以使用 thread#isInterruptedThread#interrupted 檢查中斷狀態。但是需要注意,兩個方法存在一些區別,Thread#interrupted 為靜態類方法,該方法檢測到中斷之後就會清除中斷標誌。

上面的方法我們只要加上中斷狀態判斷就也可以停止執行緒。

        Runnable interruptedTask=new Runnable() {              @Override              public void run() {                  for (int i = 0; i <Integer.MAX_VALUE ; i++) {                      // 一旦檢測到中斷標誌,停止執行緒                      if(Thread.interrupted()){                          System.out.println("interrupted!!!!");                          break;                      }                      System.out.println(i);                  }              }          };            Thread interruptThread=new Thread(interruptedTask);          interruptThread.start();          // 休眠 5 s,          TimeUnit.SECONDS.sleep(2);          // 中斷當前執行緒          interruptThread.interrupt();          // 再次休眠,觀察子執行緒          TimeUnit.SECONDS.sleep(2);      }

中斷最佳實踐

不要隨意『吃掉』中斷異常

由於中斷異常是一個 checked exception,我們不得不需要處理該異常。如果我們可以保證該異常不影響應用,我們可以直接『吃掉』這個異常。其他情況下我們需要正確處理這個異常。

最簡單的做法就是不處理該異常,直接向上拋出中斷異常,讓上層調用者決定如何處理。

但是有些情況下,卻不適合上面的做法,這種情況下我們需要在 catch 中處理中斷。如果實在不知道如何處理,那就是記錄該異常,並使用日誌方式輸出。


中斷不會停止阻塞 IO

上面我們說到,對於一些阻塞方法如 Thread#sleep ,將會拋出中斷異常。但是對於 Socket 等阻塞 IO 調用,並不會拋出這個異常。也就是說中斷並不會停止阻塞 IO 的調用。

這是因為當調用 Thread#sleep 等阻塞方法時,Java 執行緒狀態將會從 RUNNABLE 轉變為 TIMED_WAITINGWATTING。而當執行緒阻塞在 IO 讀取時,Java 執行緒實際狀態卻還是 RUNNABLE。如果你對這個執行緒狀態還有疑惑,可以閱讀下這篇文章 面試官:都說阻塞 I/O 模型將會使執行緒休眠,為什麼 Java 執行緒狀態卻是 RUNNABLE?,深入理解一下執行緒狀態。

本文首發於studyidea.cn

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

其他平台.png