停止一個線程通常意味着在線程處理任務完成之前停掉正在做的操作,也就是放棄當前的操作。
在 Java 中有以下 3 種方法可以終止正在運行的線程:
- 使用退出標志,使線程正常退出,也就是當 run() 方法完成后線程中止。
- 使用 stop() 方法強行終止線程,但是不推薦使用這個方法,該方法已被棄用。
- 使用 interrupt 方法中斷線程。
1. 使用標志位終止線程
在 run() 方法執行完畢后,該線程就終止了。但是在某些特殊的情況下,run() 方法會被一直執行;比如在服務端程序中可能會使用 while(true) { ... }
這樣的循環結構來不斷的接收來自客戶端的請求。此時就可以用修改標志位的方式來結束 run() 方法。
public class ServerThread extends Thread { //volatile修飾符用來保證其它線程讀取的總是該變量的最新的值 public volatile boolean exit = false; @Override public void run() { ServerSocket serverSocket = new ServerSocket(8080); while(!exit){ serverSocket.accept(); //阻塞等待客戶端消息 ... } } public static void main(String[] args) { ServerThread t = new ServerThread(); t.start(); ... t.exit = true; //修改標志位,退出線程 } }
2. 使用 stop() 終止線程
通過查看 JDK 的 API,我們會看到 java.lang.Thread 類型提供了一系列的方法如 start()、stop()、resume()、suspend()、destory()等方法來管理線程。但是除了 start() 之外,其它幾個方法都被聲名為已過時(deprecated)。
雖然 stop() 方法確實可以停止一個正在運行的線程,但是這個方法是不安全的,而且該方法已被棄用,最好不要使用它。
JDK 文檔中還引入用一篇文章來解釋了棄用這些方法的原因:《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》
為什么棄用stop:
- 調用 stop() 方法會立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 語句中的,並拋出ThreadDeath異常(通常情況下此異常不需要顯示的捕獲),因此可能會導致一些清理性的工作的得不到完成,如文件,數據庫等的關閉。
- 調用 stop() 方法會立即釋放該線程所持有的所有的鎖,導致數據得不到同步,出現數據不一致的問題。
例如,存在一個對象 u 持有 ID 和 NAME 兩個字段,假如寫入線程在寫對象的過程中,只完成了對 ID 的賦值,但沒來得及為 NAME 賦值,就被 stop() 導致鎖被釋放,那么當讀取線程得到鎖之后再去讀取對象 u 的 ID 和 Name 時,就會出現數據不一致的問題,如下圖:
3. 使用 interrupt() 中斷線程
現在我們知道了使用 stop() 方式停止線程是非常不安全的方式,那么我們應該使用什么方法來停止線程呢?答案就是使用 interrupt() 方法來中斷線程。
需要明確的一點的是:interrupt() 方法並不像在 for 循環語句中使用 break 語句那樣干脆,馬上就停止循環。調用 interrupt() 方法僅僅是在當前線程中打一個停止的標記,並不是真的停止線程。
也就是說,線程中斷並不會立即終止線程,而是通知目標線程,有人希望你終止。至於目標線程收到通知后會如何處理,則完全由目標線程自行決定。這一點很重要,如果中斷后,線程立即無條件退出,那么我們又會遇到 stop() 方法的老問題。
事實上,如果一個線程不能被 interrupt,那么 stop 方法也不會起作用。
我們來看一個使用 interrupt() 的例子:
public class InterruptThread1 extends Thread{ public static void main(String[] args) { try { InterruptThread1 t = new InterruptThread1(); t.start(); Thread.sleep(200); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { super.run(); for(int i = 0; i <= 200000; i++) { System.out.println("i=" + i); } } }
輸出:
從輸出的結果我們會發現 interrupt 方法並沒有停止線程 t 中的處理邏輯,也就是說即使 t 線程被設置為了中斷狀態,但是這個中斷並不會起作用,那么該如何停止線程呢?
這就需要使用到另外兩個與線程中斷有關的方法了:
public boolean Thread.isInterrupted() //判斷是否被中斷 public static boolean Thread.interrupted() //判斷是否被中斷,並清除當前中斷狀態
這兩個方法使得當前線程能夠感知到是否被中斷了(通過檢查標志位)。
所以如果希望線程 t 在中斷后停止,就必須先判斷是否被中斷,並為它增加相應的中斷處理代碼:
@Override public void run() { super.run(); for(int i = 0; i <= 200000; i++) { //判斷是否被中斷 if(Thread.currentThread().isInterrupted()){ //處理中斷邏輯 break; } System.out.println("i=" + i); } }
輸出結果,for 循環在執行完成前就提前結束了:
在上面這段代碼中,我們增加了 Thread.isInterrupted() 來判斷當前線程是否被中斷了,如果是,則退出 for 循環,結束線程。
這種方式看起來與之前介紹的“使用標志位終止線程”非常類似,但是在遇到 sleep() 或者 wait() 這樣的操作,我們只能通過中斷來處理了。
public static native void sleep(long millis) throws InterruptedException
Thread.sleep() 方法會拋出一個 InterruptedException 異常,當線程被 sleep() 休眠時,如果被中斷,這會就拋出這個異常。
(注意:Thread.sleep() 方法由於中斷而拋出的異常,是會清除中斷標記的。)