對Java中interrupt、interrupted和isInterrupted的理解(轉(zhuǎn)載)

今天在看到Thread類的isInterrupted方法可以獲取線程的中斷狀態(tài):


image.png

于是寫了個(gè)例子想驗(yàn)證一下:

public class Interrupt {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Worker());
        t.start();
        
        Thread.sleep(200);
        t.interrupt();
        
        System.out.println("Main thread stopped.");
    }
    
    public static class Worker implements Runnable {
        public void run() {
            System.out.println("Worker started.");
            
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println("Worker IsInterrupted: " + 
                        Thread.currentThread().isInterrupted());
            }
            
            System.out.println("Worker stopped.");
        }
    }
}

內(nèi)容很簡答:主線程main啟動(dòng)了一個(gè)子線程Worker,然后讓worker睡500ms,而main睡200ms,之后main調(diào)用worker線程的interrupt方法去中斷worker,worker被中斷后打印中斷的狀態(tài)。下面是執(zhí)行結(jié)果:

Worker started.
Main thread stopped.
Worker IsInterrupted: false
Worker stopped.

Worker明明已經(jīng)被中斷,而isInterrupted()方法竟然返回了false,為什么呢?
在stackoverflow上搜索了一圈之后,發(fā)現(xiàn)有網(wǎng)友提到:可以查看拋出InterruptedException方法的JavaDoc(或源代碼),于是我查看了Thread.sleep方法的文檔,doc中是這樣描述這個(gè)InterruptedException異常的:

InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

注意到后面這句“當(dāng)拋出這個(gè)異常的時(shí)候,中斷狀態(tài)已被清除”。所以isInterrupted()方法應(yīng)該返回false。可是有的時(shí)候,我們需要isInterrupted這個(gè)方法返回true,怎么辦呢?這里就要先說說interrupt, interrupted和isInterrupted的區(qū)別了:
interrupt方法是用于中斷線程的,調(diào)用該方法的線程的狀態(tài)將被置為"中斷"狀態(tài)。注意:線程中斷僅僅是設(shè)置線程的中斷狀態(tài)位,不會(huì)停止線程。需要用戶自己去監(jiān)視線程的狀態(tài)為并做處理。支持線程中斷的方法(也就是線程中斷后會(huì)拋出InterruptedException的方法,比如這里的sleep,以及Object.wait等方法)就是在監(jiān)視線程的中斷狀態(tài),一旦線程的中斷狀態(tài)被置為“中斷狀態(tài)”,就會(huì)拋出中斷異常。這個(gè)觀點(diǎn)可以通過這篇文章證實(shí):

interrupt() merely sets the thread's interruption status. Code running in the interrupted thread can later poll the interrupted status to see if it has been requested to stop what it is doing

再來看看interrupted方法的實(shí)現(xiàn):

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

和isInterrupted的實(shí)現(xiàn):

public boolean isInterrupted() {
    return isInterrupted(false);
}

這兩個(gè)方法一個(gè)是static的,一個(gè)不是,但實(shí)際上都是在調(diào)用同一個(gè)方法,只是interrupted方法傳入的參數(shù)為true,而iInterrupted傳入的參數(shù)為false。那么這個(gè)參數(shù)到底是什么意思呢?來看下這個(gè)isInterrupted(boolean)方法的實(shí)現(xiàn):

/**
* Tests if some Thread has been interrupted.  The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);

這是一個(gè)native方法,看不到源碼沒有關(guān)系,參數(shù)名字ClearInterrupted已經(jīng)清楚的表達(dá)了該參數(shù)的作用----是否清除中斷狀態(tài)。方法的注釋也清晰的表達(dá)了“中斷狀態(tài)將會(huì)根據(jù)傳入的ClearInterrupted參數(shù)值確定是否重置”。所以,靜態(tài)方法interrupted將會(huì)清除中斷狀態(tài)(傳入的參數(shù)ClearInterrupted為true),而實(shí)例方法isInterrupted則不會(huì)(傳入的參數(shù)ClearInterrupted為false)。

回到剛剛的問題:很明顯,如果要isInterrupted這個(gè)方法返回true,通過在調(diào)用isInterrupted方法之前再次調(diào)用interrupt()方法來恢復(fù)這個(gè)中斷的狀態(tài)即可:

public class Interrupt  {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Worker());
        t.start();
        
        Thread.sleep(200);
        t.interrupt();
        
        System.out.println("Main thread stopped.");
    }
    
    public static class Worker implements Runnable {
        public void run() {
            System.out.println("Worker started.");
            
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread curr = Thread.currentThread();
                //再次調(diào)用interrupt方法中斷自己,將中斷狀態(tài)設(shè)置為“中斷”
                curr.interrupt();
                System.out.println("Worker IsInterrupted: " + curr.isInterrupted());
                System.out.println("Worker IsInterrupted: " + curr.isInterrupted());
                System.out.println("Static Call: " + Thread.interrupted());//clear status
                System.out.println("---------After Interrupt Status Cleared----------");
                System.out.println("Static Call: " + Thread.interrupted());
                System.out.println("Worker IsInterrupted: " + curr.isInterrupted());
                System.out.println("Worker IsInterrupted: " + curr.isInterrupted());
            }
                System.out.println("Worker stopped.");
        }
    }
}

執(zhí)行結(jié)果:

Worker started.
Main thread stopped.
Worker IsInterrupted: true
Worker IsInterrupted: true
Static Call: true
---------After Interrupt Status Cleared----------
Static Call: false
Worker IsInterrupted: false
Worker IsInterrupted: false
Worker stopped.

從執(zhí)行結(jié)果也可以看到,前兩次調(diào)用isInterrupted方法都返回true,說明isInterrupted方法不會(huì)改變線程的中斷狀態(tài),而接下來調(diào)用靜態(tài)的interrupted()方法,第一次返回了true,表示線程被中斷,第二次則返回了false,因?yàn)榈谝淮握{(diào)用的時(shí)候已經(jīng)清除了中斷狀態(tài)。最后兩次調(diào)用isInterrupted()方法就肯定返回false了。

那么,在什么場景下,我們需要在catch塊里面中斷線程(重置中斷狀態(tài))呢?

答案是:如果不能拋出InterruptedException(就像這里的Thread.sleep語句放在了Runnable的run方法中,這個(gè)方法不允許拋出任何受檢查的異常),但又想告訴上層調(diào)用者這里發(fā)生了中斷的時(shí)候,就只能在catch里面重置中斷狀態(tài)了。

以下內(nèi)容來自:Dealing with InterruptedException

If you catch InterruptedException but cannot rethrow it, you should preserve evidence that the interruption occurred so that code higher up on the call stack can learn of the interruption and respond to it if it wants to. This task is accomplished by calling interrupt() to "reinterrupt" the current thread, as shown in Listing 3.

Listing 3: Restoring the interrupted status after catching InterruptedException

public class TaskRunner implements Runnable {
    private BlockingQueue<Task> queue;
 
    public TaskRunner(BlockingQueue<Task> queue) { 
        this.queue = queue; 
    }
 
    public void run() { 
        try {
             while (true) {
                 Task task = queue.take(10, TimeUnit.SECONDS);
                 task.execute();
             }
         } catch (InterruptedException e) { 
             // Restore the interrupted status
             Thread.currentThread().interrupt();
         }
    }
}

那么問題來了:為什么要在拋出InterruptedException的時(shí)候清除掉中斷狀態(tài)呢?

這個(gè)問題沒有找到官方的解釋,估計(jì)只有Java設(shè)計(jì)者們才能回答了。但這里的解釋似乎比較合理:一個(gè)中斷應(yīng)該只被處理一次(你catch了這個(gè)InterruptedException,說明你能處理這個(gè)異常,你不希望上層調(diào)用者看到這個(gè)中斷)。

對于這個(gè)問題,如果有更好的解釋,歡迎留言討論。若有侵權(quán),聯(lián)系刪除。謝謝。

尊重原創(chuàng) 原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容