一、原理介紹:java
使用interrupt來通知,而不是強制。安全
在Java中,最好的中止線程的方式是使用中斷 Interrupt,可是這僅僅是會通知到被終止的線程「你該中止運行了」,被終止的線程自身擁有決定權(決定是否、以及什麼時候中止),這依賴於請求中止方和被中止方都遵照一種約定好的編碼規範。數據結構
任務和線程的啓動很容易。在大多數時候,咱們都會讓它們運行直到結東,或者讓它們自行中止。然而有時候咱們但願提早結東任務或線程或許是由於用戶取消了操做,或者服務要被快速關閉,或者是運行超時或出錯了。要使任務和線程能安全、快速、可靠地中止下來,並非一件容易的事。Java沒有提供任何機制來安全地終止線程。但它提供了中斷( Interruption這是一種協做機制,可以使一個線程終止另外一個線程的當前工做)。ide
這種協做式的方法是必要的,咱們不多但願某個任務、線程或服務當即中止,由於這種當即中止會使共享的數據結構處於不一致的狀態。相反,在編寫任務和服務時可使用一種協做的方式:當須要中止時,它們首先會清除當前正在執行的工做,而後再結束。這提供了更好的靈活性,由於任務自己的代碼比發出取消請求的代碼更清楚如何執行清除工做。函數
生命週期結束(End-of-Lifecycle)的問題會使任務、服務以及程序的設計和實現等過程變得複雜而這個在程序設計中很是重要的要素卻常常被忽略。一個在行爲良好的軟件與勉強運的軟件之間的最主要區別就是,行爲良好的軟件能很完善地處理失敗、關閉和取消等過程。oop
二、如何正確中止線程編碼
三、使用interrupt中止的幾種狀況。spa
/** * @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())設計
四、若是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等函數阻塞線程後,會將該線程的阻塞標誌清除,這就致使即便通知終端信號給線程了,線程檢測不出
五、實際開發中處理終端的最佳方法:
/** * @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),頂層方法必須處理該異常,遵免了漏掉或者被吞掉的狀況,加強了代碼的健壯性。