interrupt、interrupted和isInterrupted的區別

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

於是寫了個例子想驗證一下:

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.");
        }
    }
}

內容很簡答:主線程main啓動了一個子線程Worker,然後讓worker睡500ms,而main睡200ms,之後main調用worker線程的interrupt方法去中斷worker,worker被中斷後打印中斷的狀態。下面是執行結果:

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

Worker明明已經被中斷,而isInterrupted()方法竟然返回了false,爲什麼呢?

在stackoverflow上搜索了一圈之後,發現有網友提到:可以查看拋出InterruptedException方法的JavaDoc(或源代碼),於是我查看了Thread.sleep方法的文檔,doc中是這樣描述這個InterruptedException異常的:

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

注意到後面這句“當拋出這個異常的時候,中斷狀態已被清除”。所以isInterrupted()方法應該返回false。可是有的時候,我們需要isInterrupted這個方法返回true,怎麼辦呢?這裏就要先說說interrupt, interrupted和isInterrupted的區別了:

interrupt方法是用於中斷線程的,調用該方法的線程的狀態將被置爲”中斷”狀態。注意:線程中斷僅僅是設置線程的中斷狀態位,不會停止線程。需要用戶自己去監視線程的狀態爲並做處理。支持線程中斷的方法(也就是線程中斷後會拋出InterruptedException的方法,比如這裏的sleep,以及Object.wait等方法)就是在監視線程的中斷狀態,一旦線程的中斷狀態被置爲“中斷狀態”,就會拋出中斷異常。這個觀點可以通過這篇文章證實:

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方法的實現:

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

和isInterrupted的實現:

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

這兩個方法一個是static的,一個不是,但實際上都是在調用同一個方法,只是interrupted方法傳入的參數爲true,而inInterrupted傳入的參數爲false。那麼這個參數到底是什麼意思呢?來看下這個isInterrupted(boolean)方法的實現:

/**
 * 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);

這是一個native方法,看不到源碼沒有關係,參數名字ClearInterrupted已經清楚的表達了該參數的作用—-是否清除中斷狀態。方法的註釋也清晰的表達了“中斷狀態將會根據傳入的ClearInterrupted參數值確定是否重置”。所以,靜態方法interrupted將會清除中斷狀態(傳入的參數ClearInterrupted爲true),而實例方法isInterrupted則不會(傳入的參數ClearInterrupted爲false)。

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

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();
                //再次調用interrupt方法中斷自己,將中斷狀態設置爲“中斷”
                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.");
        }
    }
}

執行結果:

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.
從執行結果也可以看到,前兩次調用isInterrupted方法都返回true,說明isInterrupted方法不會改變線程的中斷狀態,而接下來調用靜態的interrupted()方法,第一次返回了true,表示線程被中斷,第二次則返回了false,因爲第一次調用的時候已經清除了中斷狀態。最後兩次調用isInterrupted()方法就肯定返回false了。

那麼,在什麼場景下,我們需要在catch塊裏面中斷線程(重置中斷狀態)呢?

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

以下內容來自: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的時候清除掉中斷狀態呢?

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

對於這個問題,如果有更好的解釋,歡迎留言討論。


                                                       轉載自擺渡的人
發佈了28 篇原創文章 · 獲贊 8 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章