如何正確停止執行緒

  • 2019 年 11 月 10 日
  • 筆記

1、原理介紹:

使用interrupt來通知,而不是強制

在Java中,最好的停止執行緒的方式是使用中斷 Interrupt,但是這僅僅是會通知到被終止的執行緒“你該停止運行了”,被終止的執行緒自身擁有決定權(決定是否、以及何時停止),這依賴於請求停止方和被停止方都遵守一種約定好的編碼規範。

任務和執行緒的啟動很容易。在大多數時候,我們都會讓它們運行直到結東,或者讓它們自行停止。然而有時候我們希望提前結東任務或執行緒或許是因為用戶取消了操作,或者服務要被快速關閉,或者是運行超時或出錯了。要使任務和執行緒能安全、快速、可靠地停止下來,並不是一件容易的事。Java沒有提供任何機制來安全地終止執行緒。但它提供了中斷( Interruption這是一種協作機制,能夠使一個執行緒終止另一個執行緒的當前工作)。

這種協作式的方法是必要的,我們很少希望某個任務、執行緒或服務立即停止,因為這種立即停止會使共享的數據結構處於不一致的狀態。相反,在編寫任務和服務時可以使用一種協作的方式:當需要停止時,它們首先會清除當前正在執行的工作,然後再結束。這提供了更好的靈活性,因為任務本身的程式碼比發出取消請求的程式碼更清楚如何執行清除工作。

生命周期結束(End-of-Lifecycle)的問題會使任務、服務以及程式的設計和實現等過程變得複雜而這個在程式設計中非常重要的要素卻經常被忽略。一個在行為良好的軟體與勉強運的軟體之間的最主要區別就是,行為良好的軟體能很完善地處理失敗、關閉和取消等過程。


 

2、如何正確停止執行緒

  •   執行緒通常在什麼情況停止普通情況。
    • 執行完所有程式碼
    • 有異常未捕獲。

 


 

3、使用interrupt停止的幾種情況。

  • 最普遍的情況
/**   * @data 2019/11/9 - 下午8:07   * 描述:run方法內沒有sleep或wait方法時,停止執行緒   */  public class RightWayStopThreadWithoutSleep implements Runnable{      @Override      public void run() {          int num = 0;          while(num<=Integer.MAX_VALUE/2 && !Thread.currentThread().isInterrupted()){              if(num%10000 == 0)                  System.out.println(num+"是10000的倍數");              num++;          }          System.out.println("任務結束");      }        public static void main(String[] args) throws InterruptedException {          Thread thread = new Thread(new RightWayStopThreadWithoutSleep());          thread.start();          thread.sleep(1000);          thread.interrupt();      }  }

  • 當停止執行緒遇到執行緒阻塞
/**   * @data 2019/11/9 - 下午8:07   * 描述:帶有sleep的中斷執行緒的方法   */  public class RightWayStopThreadWithSleep {      public static void main(String[] args) throws InterruptedException {          Runnable runnable = ()->{              int num = 0;              try {                  while (num<=300 && !Thread.currentThread().isInterrupted()){                      if(num%100 == 0)                          System.out.println(num+"是100的倍數");                        num++;                  }                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }          };            Thread thread = new Thread(runnable);          thread.start();          Thread.sleep(500);          thread.interrupt();      }  }

結果:
0是100的倍數 100是100的倍數 200是100的倍數 300是100的倍數 java.lang.InterruptedException: sleep interrupted at java.base
/java.lang.Thread.sleep(Native Method) at threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18) at java.base/java.lang.Thread.run(Thread.java:844)

sleep、wait等一些方法使執行緒阻塞,當執行緒收到通知interrupt時候,這些方法處理該通知的方法是拋出InterruptedException異常。

  • 如果執行緒每次迭代後都阻塞
/**   * @data 2019/11/10 - 上午9:13   * 描述:如果每次執行過程中,每次循環中都調用sleep或wait等方法時,那麼不需要再每次迭代過程中檢查是否已中斷。   */  public class RightWayStopTHreadWithSleepEveryLoop {      public static void main(String[] args) throws InterruptedException {          Runnable runnable = ()->{              int num = 0;              try {                  while (num<=10000){                      if(num%100 == 0)                          System.out.println(num+"是100的倍數");                      Thread.sleep(10);                      num++;                  }              } catch (InterruptedException e) {                  e.printStackTrace();              }          };            Thread thread = new Thread(runnable);          thread.start();          Thread.sleep(5000);          thread.interrupt();      }  }

 

 在循環過程中,cpu運行速度快,大部分時間都熬在使其阻塞的方法中,所以沒必要每次迭代檢查是否已中斷-(Thread.currentThread().isInterrupted())


 

4、如果while裡面放try/catch,會導致中斷失效


/**
* @data 2019/11/10 - 上午9:24
* 描述:如果while裡面放try/catch,會導致中斷失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
while(num<10000&& !Thread.currentThread().isInterrupted()){
if(num%100 == 0){
System.out.println(num+"是100的倍數");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
 

 

結果:

0是100的倍數  100是100的倍數  200是100的倍數  300是100的倍數  400是100的倍數  java.lang.InterruptedException: sleep interrupted      at java.base/java.lang.Thread.sleep(Native Method)      at threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:17)      at java.base/java.lang.Thread.run(Thread.java:844)  500是100的倍數  600是100的倍數  700是100的倍數

 

即使加了檢查是否已中斷,但程式拋出了異常後while裡面的內容依然執行,這是因為當sleep、wait等函數阻塞執行緒後,會將該執行緒的阻塞標誌清除,這就導致即使通知終端訊號給執行緒了,執行緒檢測不出


 

5、實際開發中處理終端的最佳方法:

/**   * @data 2019/11/10 - 上午9:41   * 描述:最佳實踐:catch了InterruptedExcetion只有的優先選擇:在方法簽名中拋出異常   * 那麼在run()就會強制try/catch   */  public class RightWayStopTHreadInProd implements Runnable{        @Override      public void run() {          while (true){              try {                  System.out.println("go");                  throwInMethod();              } catch (InterruptedException e) {                  e.printStackTrace();              }          }      }        private void throwInMethod() throws InterruptedException {          Thread.sleep(2000);      }        public static void main(String[] args) throws InterruptedException {          Thread thread = new Thread(new RightWayStopTHreadInProd());          thread.start();          Thread.sleep(1000);          thread.interrupt();      }  }

 

原因:

優先選擇在方法上拋出異常。

用 throws Interruptedexception標記你的方法,不採用ty語句塊捕獲異常,以便於該異常可以傳遞到頂層,讓run方法可以捕獲這一異常,例如:

由於run方法內無法拋出 checked Exception(只能用 try catch),頂層方法必須處理該異常,遵免了漏掉或者被吞掉的情況,增強了程式碼的健壯性。