如何優雅的 kill 線程


  kill 掉一個線程,感覺是一件很簡單的事情,比如 JAVA 中為我們提供了 stop 方法可以立即終止線程的執行,達到 kill 掉線程的目的。

  但實際上對線程的操作是一件精細活,對於一段正在執行的任務,我們不能只是簡單粗暴的勒令其停止。原因就是,線程與資源是有關聯的。

  比如,一個線程持有某個 lock ,我們在線程釋放 lock 前粗暴的停止了它的運行,那么可能導致其持有的 lock 永遠不能被釋放,等待在該 lock 上的其它線程也會永遠的阻塞在該處永遠無法繼續執行。

  再比如,一個線程正在進行 IO 操作,占用着硬盤中的某個文件,我們粗暴的終止了該線程,則這個文件便得不到釋放,會一直處於被占用狀態。

  進程是資源分配單位,線程是運行調度單位。但這並不意味着,線程可以不考慮資源的調度隨意的執行任務。恰恰相反的是,許多由線程發起申請得到的資源,只有發起申請的線程才能正確的釋放它們。因為即使其對同一進程中的其它線程可見,其它線程也並不知道釋放該資源的正確時機。

  所以我們在終止線程時,不能采用類似 stop 這種簡單粗暴的方式。應該確保在終止線程時,線程並不是立即被終止,而是將控制權交給該線程,由該線程自行判斷應該做好哪些善后工作,做完這些善后工作后再停止。

  JAVA 中的 interrupt 信號可以做到這一點,一個典型的安全終止線程的方式便是基於 interrupt 信號通知線程,以及通過 InterruptedException 異常回滾堆棧鈎出阻塞中的線程,使其停止。

  關於 JVM 是如何響應 interrupt 信號並拋出 InterruptedException 的,前面的博客有非常全面的解釋:https://www.cnblogs.com/niuyourou/p/12392942.html

  我們來看一個 interrupt 終止線程的例子,一個典型的兩步終止:

public class ThreadInterrupt extends Thread{
    @Override
    public void run(){
        int i=0;
        try {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("線程進行第 " + i + " 次工作,此時 inerrupt 標志位為:"+Thread.currentThread().isInterrupted());
                Thread.sleep(100);
                i++;
            }
            //這里可以進行善后工作,比如釋放資源
            System.out.println("線程接收到 interrupt 信號,離開工作區.此時 inerrupt 標志位為:"+Thread.currentThread().isInterrupted());
        }catch (InterruptedException inE){
            //這里可以進行善后工作,比如釋放資源
            System.out.println("線程在 sleep 的過程中被 interrupted,此時 interrupt 標志位為:"+Thread.currentThread().isInterrupted());
        }
    }
}

  線程每執行完一次任務,都會檢查一下是否有 interrupt 信號,如果有則進行善后工作並退出線程。

  同時線程對 InterruptedException 進行了捕獲,如果在 sleep/wait/join 過程中收到 interrupt 信號,則回滾堆棧到 try 處,進入 catch 塊進行異常的處理,我們可以在catch 塊中進行善后工作。

  這樣無論是運行狀態還是阻塞狀態(因為 sleep/wait/join 陷入的阻塞,因為我們只有通過這些方法可以使線程陷入阻塞。synchronized 等方法阻塞或喚醒線程是由 JVM 控制的,由 JVM 保證其正確性。)下的線程,都可以及時的響應我們的 interrupt 信號。

  我們進行一下測試:

public class Test {
    public static void main(String[] args) throws InterruptedException{
        Thread test1=new ThreadInterrupt();
        test1.start();
        Thread.sleep(1000);
        test1.interrupt();
    }
}

  看一下效果,在 sleep 過程中接收到了 interrupt 信號:

  在正常工作時接收到了 interrupt 信號:

   我們可以看到,兩次被 interrupt 線程表現並不同。

  sleep 情況下進入 catch 塊后,interrupt 狀態被重置為了 false,而正常狀態下被中斷時 interrupt 狀態未被重置,依然是true。

  因為 interrupt 的本質是一個信號,正常情況下我們對一個信號進行了處理應該將其移出信號隊列(當然這里不是隊列),避免對其重復處理。

  sleep 拋出異常前 JVM 幫助我們做到了這一點,我們在對信號進行處理時,也應該手動將其重置。

  isInterrupted 只能獲取信號狀態,不會重置它。interrupted 可以返回信號的當前狀態,如果未 true,返回后會將其重置為 false 。我們可以使用 interrupted 方法代替 isInterrupted 方法:

public class ThreadInterrupt extends Thread{
    @Override
    public void run(){
        int i=0;
        try {
            while (!Thread.interrupted()) {
                System.out.println("線程進行第 " + i + " 次工作,此時 inerrupt 標志位為:"+Thread.currentThread().isInterrupted());
                Thread.sleep(1);
                i++;
            }
            //這里可以進行善后工作,比如釋放資源
            System.out.println("線程接收到 interrupt 信號,離開工作區.此時 inerrupt 標志位為:"+Thread.currentThread().isInterrupted());
        }catch (InterruptedException inE){
            //這里可以進行善后工作,比如釋放資源
            System.out.println("線程在 sleep 的過程中被 interrupted,此時 interrupt 標志位為:"+Thread.currentThread().isInterrupted());
        }
    }
}

  這樣再來看效果:

  可以看到,在響應了中斷信號后,interrupted 方法幫我們將 interrupt 狀態重置為了 false。 

   第 50 次工作時,檢測到了 interrupt 信號,但線程還是將其執行完畢后才退出了工作區,這也證實了該方法的可靠性,使線程盡量安全的退出。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM