線程終止有兩種情況:
1、線程的任務執行完成
2、線程在執行任務過程中發生異常
這兩者屬於線程自行終止,如何讓線程 A 把線程 B 終止呢?
Java 中 Thread 類有一個 stop() 方法,可以終止線程,不過這個方法會讓線程直接終止,在執行的任務立即終止,未執行的任務無法反饋,所以 stop() 方法已經不建議使用。
既然 stop() 方法如此粗暴,不建議使用,我們如何優雅地結束線程呢?
線程只有從 runnable 狀態(可運行/運行狀態) 才能進入terminated 狀態(終止狀態),如果線程處於 blocked、waiting、timed_waiting 狀態(休眠狀態),就需要通過 Thread 類的 interrupt() 方法,讓線程從休眠狀態進入 runnable 狀態,從而結束線程。
當線程進入 runnable 狀態之后,通過設置一個標識位,線程在合適的時機,檢查該標識位,發現符合終止條件,自動退出 run () 方法,線程終止。
如我們模擬一個系統監控任務線程,代碼如下
package constxiong.concurrency.a007; /** * 模擬系統監控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//測試系統監控器 } /** * 測試系統監控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //運行 10 秒后停止監控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("監控任務啟動 10 秒后,停止..."); sm.stop(); } } /** * 系統監控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 啟動一個線程監控系統 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判斷當前線程是否被打斷 System.out.println("正在監控系統..."); try { Thread.sleep(3 * 1000L);//執行 3 秒 System.out.println("任務執行 3 秒"); System.out.println("監控的系統正常!"); } catch (InterruptedException e) { System.out.println("任務執行被中斷..."); } } }); t.start(); } void stop() { t.interrupt(); } }
執行結果
正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 監控任務啟動 10 秒后,停止... 任務執行被中斷... 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... . . .
從代碼和執行結果我們可以看出,系統監控器 start() 方法會創建一個線程執行監控系統的任務,每個任務查詢系統情況需要 3 秒鍾,在監控 10 秒鍾后,主線程向監控器發出停止指令。
但是結果卻不是我們期待的,10 秒后並沒有終止了監控器,任務還在執行。
原因在於,t.interrupt() 方法讓處在休眠狀態的語句 Thread.sleep(3 * 1000L); 拋出異常,同時被捕獲,此時 JVM 的異常處理會清除線程的中斷狀態,導致任務一直在執行。
處理辦法是,在捕獲異常后,繼續重新設置中斷狀態,代碼如下
package constxiong.concurrency.a007; /** * 模擬系統監控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//測試系統監控器 } /** * 測試系統監控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //運行 10 秒后停止監控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("監控任務啟動 10 秒后,停止..."); sm.stop(); } } /** * 系統監控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 啟動一個線程監控系統 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判斷當前線程是否被打斷 System.out.println("正在監控系統..."); try { Thread.sleep(3 * 1000L);//執行 3 秒 System.out.println("任務執行 3 秒"); System.out.println("監控的系統正常!"); } catch (InterruptedException e) { System.out.println("任務執行被中斷..."); Thread.currentThread().interrupt();//重新設置線程為中斷狀態 } } }); t.start(); } void stop() { t.interrupt(); } }
執行結果如預期
正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 監控任務啟動 10 秒后,停止... 任務執行被中斷...
到這里還沒有結束,我們用 Thread.sleep(3 * 1000L); 去模擬任務的執行,在實際情況中,一般是調用其他服務的代碼,如果出現其他異常情況沒有成功設置線程的中斷狀態,線程將一直執行下去,顯然風險很高。所以,需要用一個線程終止的標識來代替 Thread.currentThread().isInterrupted()。
修改代碼如下
package constxiong.concurrency.a007; /** * 模擬系統監控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//測試系統監控器 } /** * 測試系統監控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //運行 10 秒后停止監控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("監控任務啟動 10 秒后,停止..."); sm.stop(); } } /** * 系統監控器 * @author ConstXiong */ class SystemMonitor { private Thread t; private volatile boolean stop = false; /** * 啟動一個線程監控系統 */ void start() { t = new Thread(() -> { while (!stop) {//判斷當前線程是否被打斷 System.out.println("正在監控系統..."); try { Thread.sleep(3 * 1000L);//執行 3 秒 System.out.println("任務執行 3 秒"); System.out.println("監控的系統正常!"); } catch (InterruptedException e) { System.out.println("任務執行被中斷..."); Thread.currentThread().interrupt();//重新設置線程為中斷狀態 } } }); t.start(); } void stop() { stop = true; t.interrupt(); } }
執行結果
正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 任務執行 3 秒 監控的系統正常! 正在監控系統... 監控任務啟動 10 秒后,停止... 任務執行被中斷...
到這里基本算是優雅地讓線程終止了。
使用 volatile 修飾 stop 變量有必要嗎?作用是什么?
線程只能通過 runnable 狀態到 terminated 狀態,那線程狀態是如何變化的呢?
我們后續繼續實踐。