【轉】理解java執行緒的中斷(interrupt)

陳俊成發表於2016-09-28

執行緒物件.interrupt()並不會真正的中斷,只是修改了它的中斷訊號。

原文地址:點選進入

一個執行緒在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重後果比如會帶著自己所持有的鎖而永遠的休眠,遲遲不歸還鎖等。 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了

那麼不能直接把一個執行緒搞掛掉, 但有時候又有必要讓一個執行緒死掉, 或者讓它結束某種等待的狀態 該怎麼辦呢?一個比較優雅而安全的做法是:使用等待/通知機制或者給那個執行緒一箇中斷訊號, 讓它自己決定該怎麼辦。

等待/通過機制在另一篇部落格中詳細的介紹了。這裡我們理解執行緒中斷的使用場景和使用時的注意事項,最後使用Demo來理解。

中斷執行緒的使用場景:

在某個子執行緒中為了等待一些特定條件的到來, 你呼叫了Thread.sleep(10000), 預期執行緒睡10秒之後自己醒來, 但是如果這個特定條件提前到來的話, 來通知一個處於Sleep的執行緒。又比如說.執行緒通過呼叫子執行緒的join方法阻塞自己以等待子執行緒結束, 但是子執行緒執行過程中發現自己沒辦法在短時間內結束, 於是它需要想辦法告訴主執行緒別等我了. 這些情況下, 就需要中斷.

斷是通過呼叫Thread.interrupt()方法來做的. 這個方法通過修改了被呼叫執行緒的中斷狀態來告知那個執行緒, 說它被中斷了. 對於非阻塞中的執行緒, 只是改變了中斷狀態, 即Thread.isInterrupted()將返回true; 對於可取消的阻塞狀態中的執行緒, 比如等待在這些函式上的執行緒, Thread.sleep(), Object.wait(), Thread.join(), 這個執行緒收到中斷訊號後, 會丟擲InterruptedException, 同時會把中斷狀態置回為true.但呼叫Thread.interrupted()會對中斷狀態進行復位。

對非阻塞中的執行緒中斷的Demo:

public class Thread3 extends Thread{
    public void run(){  
        while(true){  
            if(Thread.currentThread().isInterrupted()){  
                System.out.println("Someone interrupted me.");  
            }  
            else{  
                System.out.println("Thread is Going...");  
            }
        }  
    }  

    public static void main(String[] args) throws InterruptedException {  
        Thread3 t = new Thread3();  
        t.start();  
        Thread.sleep(3000);  
        t.interrupt();  
    }  
}  

分析如上程式的結果:
在main執行緒sleep的過程中由於t執行緒中isInterrupted()為false所以不斷的輸出”Thread is going”。當呼叫t執行緒的interrupt()後t執行緒中isInterrupted()為true。此時會輸出Someone interrupted me.而且執行緒並不會因為中斷訊號而停止執行。因為它只是被修改一箇中斷訊號而已。

首先我們看看interrupt究竟在幹什麼。
當我們呼叫t.interrput()的時候,執行緒t的中斷狀態(interrupted status) 會被置位。我們可以通過Thread.currentThread().isInterrupted() 來檢查這個布林型的中斷狀態。

在Core Java中有這樣一句話:”沒有任何語言方面的需求要求一個被中斷的程式應該終止。中斷一個執行緒只是為了引起該執行緒的注意,被中斷執行緒可以決定如何應對中斷 “。好好體會這句話的含義,看看下面的程式碼:

    //Interrupted的經典使用程式碼    
    public void run(){    
            try{    
                 ....    
                 while(!Thread.currentThread().isInterrupted()&& more work to do){    
                        // do more work;    
                 }    
            }catch(InterruptedException e){    
                        // thread was interrupted during sleep or wait    
            }    
            finally{    
                       // cleanup, if required    
            }    
    }    

很顯然,在上面程式碼中,while迴圈有一個決定因素就是需要不停的檢查自己的中斷狀態。當外部執行緒呼叫該執行緒的interrupt 時,使得中斷狀態置位即變為true。這是該執行緒將終止迴圈,不在執行迴圈中的do more work了。

這說明: interrupt中斷的是執行緒的某一部分業務邏輯,前提是執行緒需要檢查自己的中斷狀態(isInterrupted())

但是當執行緒被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時。呼叫它的interrput()方法。可想而知,沒有佔用CPU執行的執行緒是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常

    /*  
    * 如果執行緒被阻塞,它便不能核查共享變數,也就不能停止。這在許多情況下會發生,例如呼叫 
    * Object.wait()、ServerSocket.accept()和DatagramSocket.receive()時,他們都可能永 
    * 久的阻塞執行緒。即使發生超時,在超時期滿之前持續等待也是不可行和不適當的,所以,要使 
    * 用某種機制使得執行緒更早地退出被阻塞的狀態。很不幸運,不存在這樣一種機制對所有的情況 
    * 都適用,但是,根據情況不同卻可以使用特定的技術。使用Thread.interrupt()中斷執行緒正 
    * 如Example1中所描述的,Thread.interrupt()方法不會中斷一個正在執行的執行緒。這一方法 
    * 實際上完成的是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。更 
    * 確切的說,如果執行緒被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼, 
    * 它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。因此, 
    * 如果執行緒被上述幾種方法阻塞,正確的停止執行緒方式是設定共享變數,並呼叫interrupt()(注 
    * 意變數應該先設定)。如果執行緒沒有被阻塞,這時呼叫interrupt()將不起作用;否則,執行緒就 
    * 將得到異常(該執行緒必須事先預備好處理此狀況),接著逃離阻塞狀態。在任何一種情況中,最 
    * 後執行緒都將檢查共享變數然後再停止。下面示例描述了該技術。 
    * */  
    package Concurrency.Interrupt;  

    class Example3 extends Thread {  

    volatile boolean stop = false;  

    public static void main(String args[]) throws Exception {  
    Example3 thread = new Example3();  

    System.out.println("Starting thread...");  
    thread.start();  

    Thread.sleep(3000);  

    System.out.println("Asking thread to stop...");  

    /* 
    * 如果執行緒阻塞,將不會檢查此變數,呼叫interrupt之後,執行緒就可以儘早的終結被阻  
    * 塞狀 態,能夠檢查這一變數。 
    * */  
    thread.stop = true;  

    /* 
    * 這一方法實際上完成的是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退 
    * 出阻 塞的狀態 
    * */  
    thread.interrupt();  

    Thread.sleep(3000);  
    System.out.println("Stopping application...");  
    System.exit(0);  
    }  

    public void run() {  
    while (!stop) {  
    System.out.println("Thread running...");  
    try {  
    Thread.sleep(2000);  
    } catch (InterruptedException e) {  
    // 接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態  
    System.out.println("Thread interrupted...");  
    }  
    }  

    System.out.println("Thread exiting under request...");  
    }  
    }  
    /* 
    * 把握幾個重點:stop變數、run方法中的sleep()、interrupt()、InterruptedException。串接起 
    * 來就是這個意思:當我們在run方法中呼叫sleep(或其他阻塞執行緒的方法)時,如果執行緒阻塞的 
    * 時間過長,比如10s,那在這10s內,執行緒阻塞,run方法不被執行,但是如果在這10s內,stop被 
    * 設定成true,表明要終止這個執行緒,但是,現線上程是阻塞的,它的run方法不能執行,自然也就 
    * 不能檢查stop,所 以執行緒不能終止,這個時候,我們就可以用interrupt()方法了:我們在 
    * thread.stop = true;語句後呼叫thread.interrupt()方法, 該方法將線上程阻塞時丟擲一箇中斷 
    * 訊號,該訊號將被catch語句捕獲到,一旦捕獲到這個訊號,執行緒就提前終結自己的阻塞狀態,這 
    * 樣,它就能夠 再次執行run 方法了,然後檢查到stop = true,while迴圈就不會再被執行,在執 
    * 行了while後面的清理工作之後,run方法執行完 畢,執行緒終止。 
    * */  

當程式碼呼叫中須要丟擲一個InterruptedException, 你可以選擇把中斷狀態復位, 也可以選擇向外丟擲InterruptedException, 由外層的呼叫者來決定.

**不是所有的阻塞方法收到中斷後都可以取消阻塞狀態, 輸入和輸出流類會阻塞等待 I/O 完成,但是它們不丟擲 InterruptedException,而且在被中斷的情況下也不會退出阻塞狀態.
嘗試獲取一個內部鎖的操作(進入一個 synchronized 塊)是不能被中斷的,但是 ReentrantLock 支援可中斷的獲取模式即 tryLock(long time, TimeUnit unit)。**

相關文章