如何優雅地停止一個線程?


線程終止有兩種情況:

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 狀態,那線程狀態是如何變化的呢?

我們后續繼續實踐。


 來一道刷了進BAT的面試題?


免責聲明!

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



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