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方法,都有一定的侷限性,無法終止任意的執行緒。下一篇論文將會探索如何終止一個任意的執行緒。