中止線程的方法
如何中止一個(gè)正在執(zhí)行的線程贝奇?
Thread#stop()
java.lang.Thread#stop
:強(qiáng)制線程停止執(zhí)行晦闰。
從JDK1.2開始便脊,該API已被棄用胞此,因?yàn)樗?strong>可能導(dǎo)致線程安全問題款票。
Thread#stop()
方法通過拋出java.lang.ThreadDeath
異常來達(dá)到中止線程的目的。這會(huì)使線程釋放它持的有全部鎖枷颊,如果之前被鎖保護(hù)的對(duì)象已經(jīng)處于不一致狀態(tài)戳杀,那么這些狀態(tài)將立即對(duì)其它線程可見。當(dāng)其它線程操作一個(gè)損壞的對(duì)象時(shí)夭苗,將會(huì)導(dǎo)致不可預(yù)測(cè)的后果信卡!
而且,默認(rèn)情況下题造,ThreadDeath
錯(cuò)誤不會(huì)被打印或通知應(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()方法時(shí)界赔,可在此處斷點(diǎn)調(diào)試
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
這意味著丢习,如果你使用stop()
方法中止線程,你的應(yīng)用不會(huì)收到任何通知淮悼。此時(shí)咐低,你的某些對(duì)象可能已經(jīng)處于不一致狀態(tài),然而你對(duì)此一無(wú)所知敛惊。
能否通過捕獲ThreadDeath
異常來處理這種情況呢渊鞋?理論上是可以的,但是不推薦這么做。原因有二:
-
ThreadDeath
異澄危可能在線程執(zhí)行的任何地方拋出儡湾。那么,所有的同步方法和同步代碼塊都需要對(duì)ThreadDeath
異常捕獲處理执俩。 - 在
catch
或finally
子句中處理第一個(gè)ThreadDeath
異常時(shí)徐钠,可能會(huì)有第二個(gè)ThreadDeath
拋出,必須重復(fù)處理直到成功役首。
這樣做的代價(jià)太大尝丐,不現(xiàn)實(shí)。
既然Thread#stop()
已經(jīng)被棄用了衡奥,那么有沒有什么替代方案可以中止線程呢爹袁?
基于狀態(tài)的信號(hào)機(jī)制
在大部分使用Thread#stop()
的地方都可以用基于狀態(tài)的信號(hào)機(jī)制來替代。
在線程之間共享一個(gè)變量矮固,目標(biāo)線程定期地去檢查這個(gè)變量的狀態(tài)并據(jù)此判斷是否應(yīng)該繼續(xù)執(zhí)行失息,而其它線程就可以通過修改這個(gè)狀態(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)鍵字修飾绎秒,或者在同步方法或同步代碼塊中操作,這樣才能保證變量的修改對(duì)其它線程可見尼摹。
Thread#interrupt()
基于狀態(tài)的信號(hào)機(jī)制可以滿足大部分線程中止場(chǎng)景见芹,但是當(dāng)目標(biāo)線程處于長(zhǎng)時(shí)間等待狀態(tài)(比如在一個(gè)條件上等待)時(shí),該機(jī)制無(wú)法使用窘问。此時(shí)辆童,可以嘗試使用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會(huì)清除中斷標(biāo)志惠赫,此處再次中斷線程
Thread.currentThread().interrupt();
}
}
});
thread.start();
// 主線程休眠1s,等待目標(biāo)線程進(jìn)入睡眠
Thread.sleep(1000);
// 調(diào)用目標(biāo)線程的interrupt()方法中斷等待
thread.interrupt();
// 等待目標(biāo)線程執(zhí)行完畢
thread.join();
}
}
運(yùn)行main()函數(shù)故黑,1秒后主線程退出儿咱。這說明Thread#interrupt()
成功中止了目標(biāo)線程的睡眠。
然而场晶,并不是所有的等待都被Thread#interrupt
中止混埠,當(dāng)目標(biāo)線程處于以下等待場(chǎng)景時(shí):
- 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
方法會(huì)中斷等待(不同的情景響應(yīng)中斷后線程的中斷標(biāo)志不同)吏颖。其他情況下調(diào)用Thread#interrupt
只會(huì)設(shè)置目標(biāo)線程的中斷標(biāo)志搔体,無(wú)法中止等待狀態(tài)。此時(shí)半醉,只能根據(jù)具體場(chǎng)景來分析如何中止等待(比如疚俱,如果線程在Socket
上長(zhǎng)時(shí)間等待,可以關(guān)閉Socket
來中止等待)缩多。