Java終止線程的三種方式

中止一個線程一般意味着在線程處理任務完成以前停掉正在作的操做,也就是放棄當前的操做。html

在 Java 中有如下 3 種方法能夠終止正在運行的線程:java

  1. 使用退出標誌,使線程正常退出,也就是當 run() 方法完成後線程停止。
  2. 使用 stop() 方法強行終止線程,可是不推薦使用這個方法,該方法已被棄用。
  3. 使用 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?》oracle

爲何棄用stop:ide

  1. 調用 stop() 方法會馬上中止 run() 方法中剩餘的所有工做,包括在 catch 或 finally 語句中的,並拋出ThreadDeath異常(一般狀況下此異常不須要顯示的捕獲),所以可能會致使一些清理性的工做的得不到完成,如文件,數據庫等的關閉。
  2. 調用 stop() 方法會當即釋放該線程所持有的全部的鎖,致使數據得不到同步,出現數據不一致的問題。

例如,存在一個對象 u 持有 ID 和 NAME 兩個字段,假如寫入線程在寫對象的過程當中,只完成了對 ID 的賦值,但沒來得及爲 NAME 賦值,就被 stop() 致使鎖被釋放,那麼當讀取線程獲得鎖以後再去讀取對象 u 的 ID 和 Name 時,就會出現數據不一致的問題,以下圖:ui

3. 使用 interrupt() 中斷線程

如今咱們知道了使用 stop() 方式中止線程是很是不安全的方式,那麼咱們應該使用什麼方法來中止線程呢?答案就是使用 interrupt() 方法來中斷線程線程

須要明確的一點的是:interrupt() 方法並不像在 for 循環語句中使用 break 語句那樣乾脆,立刻就中止循環。調用 interrupt() 方法僅僅是在當前線程中打一箇中止的標記,並非真的中止線程。code

也就是說,線程中斷並不會當即終止線程,而是通知目標線程,有人但願你終止。至於目標線程收到通知後會如何處理,則徹底由目標線程自行決定。這一點很重要,若是中斷後,線程當即無條件退出,那麼咱們又會遇到 stop() 方法的老問題。server

事實上,若是一個線程不能被 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() 方法因爲中斷而拋出的異常,是會清除中斷標記的。)

相關文章
相關標籤/搜索