中止線程的方法

中止線程的方法

如何中止一個正在執(zhí)行的線程?

Thread#stop()

java.lang.Thread#stop強制線程停止執(zhí)行。

從JDK1.2開始,該API已被棄用,因為它可能導(dǎo)致線程安全問題。

Thread#stop()方法通過拋出java.lang.ThreadDeath 異常來達到中止線程的目的。這會使線程釋放它持的有全部鎖,如果之前被鎖保護的對象已經(jīng)處于不一致狀態(tài),那么這些狀態(tài)將立即對其它線程可見。當(dāng)其它線程操作一個損壞的對象時,將會導(dǎo)致不可預(yù)測的后果!

而且,默認情況下,ThreadDeath錯誤不會被打印或通知應(yīng)用程序。在 java.lang.ThreadGroup中,可以看到這樣一段代碼:

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);         
        } 
        // ThreadDeath異常被忽略
        else if (!(e instanceof ThreadDeath)) { // 調(diào)用Thread#stop()方法時,可在此處斷點調(diào)試
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

這意味著,如果你使用stop()方法中止線程,你的應(yīng)用不會收到任何通知。此時,你的某些對象可能已經(jīng)處于不一致狀態(tài),然而你對此一無所知。

能否通過捕獲ThreadDeath異常來處理這種情況呢?理論上是可以的,但是不推薦這么做。原因有二:

  • ThreadDeath異??赡茉诰€程執(zhí)行的任何地方拋出。那么,所有的同步方法和同步代碼塊都需要對ThreadDeath異常捕獲處理。
  • catchfinally子句中處理第一個ThreadDeath異常時,可能會有第二個ThreadDeath拋出,必須重復(fù)處理直到成功。

這樣做的代價太大,不現(xiàn)實。

既然Thread#stop()已經(jīng)被棄用了,那么有沒有什么替代方案可以中止線程呢?

基于狀態(tài)的信號機制

在大部分使用Thread#stop()的地方都可以用基于狀態(tài)的信號機制來替代。

在線程之間共享一個變量,目標(biāo)線程定期地去檢查這個變量的狀態(tài)并據(jù)此判斷是否應(yīng)該繼續(xù)執(zhí)行,而其它線程就可以通過修改這個狀態(tài)來中止目標(biāo)線程。

public class StatusBasedSignalStop {

    private static volatile boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                // 檢查變量的值,判斷是否應(yīng)該中止執(zhí)行
                while (isRunning) {
                    System.out.println("線程狀態(tài): " + Thread.currentThread().getState());
                    try {
                        Thread.sleep(1100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 退出執(zhí)行前可以做一些善后工作,比如資源清理
            }
        });
        thread.start();
        // 3s后修改變量的值,表明目標(biāo)線程應(yīng)該中止了
        Thread.sleep(3000);
        isRunning = false;
        // 休眠1s,再看目標(biāo)線程的狀態(tài)
        Thread.sleep(1000);
        System.out.println("線程狀態(tài): " + thread.getState());
    }
}

程序輸出如下:

線程狀態(tài): RUNNABLE
線程狀態(tài): RUNNABLE
線程狀態(tài): RUNNABLE
線程狀態(tài): TERMINATED

注意:被線程共享的變量必須使用volatile關(guān)鍵字修飾,或者在同步方法或同步代碼塊中操作,這樣才能保證變量的修改對其它線程可見。

Thread#interrupt()

基于狀態(tài)的信號機制可以滿足大部分線程中止場景,但是當(dāng)目標(biāo)線程處于長時間等待狀態(tài)(比如在一個條件上等待)時,該機制無法使用。此時,可以嘗試使用Thread#interrupt()去打斷它。

public class InterruptStop {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    // 我想睡好久好久
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                    // Thread#sleep()拋出InterruptedException會清除中斷標(biāo)志,此處再次中斷線程
                    Thread.currentThread().interrupt();
                }
            }
        });
        thread.start();
        // 主線程休眠1s,等待目標(biāo)線程進入睡眠
        Thread.sleep(1000);
        // 調(diào)用目標(biāo)線程的interrupt()方法中斷等待
        thread.interrupt();
        // 等待目標(biāo)線程執(zhí)行完畢
        thread.join();
    }
}

運行main()函數(shù),1秒后主線程退出。這說明Thread#interrupt()成功中止了目標(biāo)線程的睡眠。

然而,并不是所有的等待都被Thread#interrupt中止,當(dāng)目標(biāo)線程處于以下等待場景時:

  • Object#wait();
  • Object#wait(long);
  • Object#wait(long, int);
  • Thread#join();
  • Thread#join(long);
  • Thread#sleep(long);
  • Thread#sleep(long, int);
  • java.nio.channels.InterruptibleChannel上的阻塞IO操作;
  • java.nio.channels.Selector上的阻塞IO操作;

調(diào)用Thread#interrupt方法會中斷等待(不同的情景響應(yīng)中斷后線程的中斷標(biāo)志不同)。其他情況下調(diào)用Thread#interrupt只會設(shè)置目標(biāo)線程的中斷標(biāo)志,無法中止等待狀態(tài)。此時,只能根據(jù)具體場景來分析如何中止等待(比如,如果線程在Socket上長時間等待,可以關(guān)閉Socket來中止等待)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容