在Java中如何優雅的停止一個執行緒?可別再用Thread.stop()了!

JavaBuild發表於2024-03-13

寫在開頭

經過上幾篇博文的學習,我們知道在Java中可以透過new Thread().start()建立一個執行緒,那今天我們就來思考另外一個問題:執行緒的終止
自然終止有兩種情況:

1. 執行緒的任務執行完成;
2. 執行緒在執行任務過程中發生異常。

start之後,如果執行緒沒有走到終止狀態,我們該如何停止這個執行緒呢?

為什麼stop終止不可用

翻看Thread原始碼後,發現其提供過一個stop()方法,可以用來終止執行緒,我們看一下它的原始碼。

【原始碼解析1】

@Deprecated
    public final void stop() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

這個方法使用了@Deprecated修飾,代表著它是廢棄的方法,在Java的編碼規約中,過時的方法不建議繼續使用,並且在這個方法的註釋中官方也提示說這是一個不安全的強制惡意中斷方法,會破壞執行緒的原子性。
image

因此,在這裡強烈建議大家不要再用stop方法去停止執行緒了!

如何優雅的停止一個執行緒

我們知道執行緒只有從 runnable 狀態(可執行/執行狀態) 才能進入terminated 狀態(終止狀態),如果執行緒處於 blocked、waiting、timed_waiting 狀態(休眠狀態),就需要透過 Thread 類的 interrupt() 方法,讓執行緒從休眠狀態進入 runnable 狀態,從而結束執行緒。

這裡就涉及到了一個概念“執行緒中斷”,這是一種協作機制,當其他執行緒通知需要被中斷的執行緒後,執行緒中斷的狀態被設定為 true,但是具體被要求中斷的執行緒要怎麼處理,完全由被中斷執行緒自己決定,可以在合適的時機中斷請求,也可以完全不處理繼續執行下去,這樣一來,安全性就得到了保障。

Thread類中提供執行緒中斷的方法如下:

  • Thread.interrupt():中斷執行緒。這裡的中斷執行緒並不會立即停止執行緒,而是設定執行緒的中斷狀態為 true(預設是 flase);
  • Thread.currentThread().isInterrupted():測試當前執行緒是否被中斷。執行緒的中斷狀態會受這個方法的影響,呼叫一次可以使執行緒中斷狀態變為 true,呼叫兩次會使這個執行緒的中斷狀態重新轉為 false;
  • Thread.isInterrupted():測試當前執行緒是否被中斷。與上面方法不同的是呼叫這個方法並不會影響執行緒的中斷狀態。

Ok,寫了那麼多,我們來寫一個小的demo測試一下執行緒中斷的方法。

【程式碼示例】

public class Test {
    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();
    }
}
/*系統監控器*/
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("任務執行被中斷...");
                    //重新設定執行緒為中斷狀態,保證JVM拋異常情況下,中斷狀態仍為true。
                    Thread.currentThread().interrupt();
                }
            }
        });
        t.start();
    }
    //執行緒中斷
    void stop() {
        stop = true;
        t.interrupt();
    }
}

在這裡我們先建立了一個SystemMonitor類作為系統檢測器,每3秒一迴圈的進行檢測,考慮到在Thread.currentThread().isInterrupted()可能在某些情況下中斷失效,所以我們這裡自定義一個stop變數,作為執行緒中斷的標識,檢測執行緒啟動先對標識位進行判斷。

然後,我們在Test類中寫一個測試方法,呼叫這個系統監控器,進行檢測,並設定10秒後,呼叫stop方法中斷檢測執行緒,將中斷標識stop設定為true。啟動程式碼後,我們在控制檯可以看到這樣的輸出:

正在監控系統...
任務執行 3 秒
監控的系統正常!
正在監控系統...
任務執行 3 秒
監控的系統正常!
正在監控系統...
任務執行 3 秒
監控的系統正常!
正在監控系統...
監控任務啟動 10 秒後,停止...
任務執行被中斷...

與我們的預期一樣,監控執行緒在執行了3個迴圈的檢測任務後,被成功中斷調。到這裡,我們就成功的、安全的、優雅的停止了一個執行緒啦!

結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

image

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

image

相關文章