1. 程式人生 > >Java併發程式設計番外篇(一)如何結束一個執行緒

Java併發程式設計番外篇(一)如何結束一個執行緒

本篇部落格介紹下如何結束一個執行緒。在Java 的設計中,當一個執行緒的run方法中程式碼執行結束後,該執行緒就自動結束了。但是,在我們的實際開發過程中,可能會需要手動的結束一個執行緒,此時我們應該怎麼安全地結束一個執行緒呢?

1. stop方法

根據Java的官方文件,Java Thread,該方法已被棄用。文件給出的解釋是,該方法不安全,使用stop方法結束一個執行緒會導致該執行緒釋放所有佔有的鎖。如果這些鎖保護的一些物件處於不一致的狀態,該物件會對其他執行緒不可見,可能會導致任意的行為。

同時,該文件也指出,大部分的stop使用,都可以通過修改變數的值來表明目標執行緒是否執行來實現。目標執行緒應該檢查該變數,當該變量表明應該結束的時候,該執行緒從run方法中返回(return)來結束執行。如果目標執行緒長時間等待,interrupt方法可以被用於終止該等待。

文件最後給出了一個連結,Java Thread Primitive Deprecation,來詳細解釋Thread類的stop,suspend,resume方法被棄用。

2. 標誌位結束執行緒

如果我們的執行緒執行的是週期性任務,也就說,我們的run方法內部是包含有迴圈的,我們就可以使用一個標誌位,每次迴圈的時候,判斷標誌,進而決定是否退出執行緒,如以下程式碼,

    public static class MyThread extends Thread {
        private volatile boolean stop = false;
        public
void stopThread() { this.stop = true; } @Override public void run() { // TODO Auto-generated method stub super.run(); while (!stop) { // do some thing } } }

在程式碼中,我們使用了一個stop變數,來標誌執行緒是否應該執行,注意到該變數被宣告成volatile

型別,這是為了解決多個執行緒訪問該執行緒會導致的不一致問題。
在我們的呼叫執行緒中,可以這樣使用,

        MyThread thread = new MyThread();
        thread.start();
        // ...
        thread.stopThread();

使用該方法終止執行緒需要有兩點注意的,

  • 我們的任務必須是週期性的,如果是執行一次就結束,則無法使用該方法。
  • 標誌變數需要設定為volatile型別,這是為了防止多執行緒會出現的不同步問題。

3. interrupt方法

3.1 捕獲InterruptedException異常結束執行緒

如果我們的run方法中包含有長時間的等待,比如sleep,wait,join等操作,那麼我們可以使用interrupt來中斷執行緒的等待,由於interrupt會丟擲一個InterruptedException異常,所以我們在run方法中捕獲到該異常,然後返回,即退出了該執行緒。一個簡單的例項如下,

    public static class MyThread2 extends Thread {

        @Override
        public void run() {
            super.run();

            // other code1
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            // other code2

        }
    }

根據Java的官方文件,Java interrupt,interrupt只能中斷處於等待狀態的執行緒,包括正在執行sleep,join或者wait操作的執行緒,而且中斷後,會丟擲一個InterruptedException,所以我們在程式碼中捕獲該異常,並在處理異常的程式碼中加入返回(return)語句,及當執行緒被中斷後,就退出該執行緒。

在呼叫執行緒中,我們這樣使用,

        MyThread2 thread2 = new MyThread2();
        thread2.start();
        //...
        thread2.interrupt();

然而,該方法也有兩點限制,

  • run方法中必須還有等待,比如sleep,join或者wait操作。
  • 當中斷執行緒的時候,當執行緒處於等待的時候,才能中斷,如果正在執行則不能中斷。在例子中,如果執行緒在執行code1或者code2,則該方法無法中斷該執行緒。

3.2 isInterrupted方法檢測是否被中斷

上面的通過中斷來結束執行緒有一個要求,就是執行緒等待的時候,才能被中斷,該要求顯然太過苛刻。
當我們的程式碼並沒有長時間的等待怎麼辦?是不是此時就無法通過中斷來結束執行緒了呢?
stackoverflow給出了另一種使用interrupt中斷執行緒來實現執行緒結束。他提供的程式碼如下,

    public static class MyThread3 extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            while (!isInterrupted()) {
                // do your job

            }
        }
    }

該程式碼並不是捕獲異常來判斷執行緒是否被中斷,而是通過isInterrupted判斷該執行緒是否被中斷,

該方法也有一個相同的不足,

  • run方法內部必須也要包括一個迴圈,如果是一個單一的任務,則無法中斷執行緒。

另外需要注意的一點是,上面介紹的兩個方法不能混合使用,一個錯誤的示例如下,

    public static class MyThread3 extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            while (!isInterrupted()) {
                // do your job
                System.out.println("thread is running...");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }
    }

我們執行該程式碼會發現,該執行緒會一直執行下去,這是為什麼呢?原因在於,當執行緒捕獲到異常後,會將中斷標誌位清空,也就是isInterrupted此時就無法判斷出該執行緒是被中斷的了。

通過上面的分析,我們知道,不論是標誌位或者interrupt方法,都有一定的侷限性,無法終止任意的執行緒。下一篇論文將會探索如何終止一個任意的執行緒。