中止線程的方法
如何中止一個正在執(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異常捕獲處理。 - 在
catch或finally子句中處理第一個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來中止等待)。