1. 程式人生 > >多執行緒中斷機制

多執行緒中斷機制

友情推薦:

  1. 執行緒池原理
  2. 深入Thread.sleep
  3. head first Thread.join()

在 java中啟動執行緒非常容易,大多數情況下是讓一個執行緒執行完自己的任務然後自己停掉。一個執行緒在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重後果,比如會帶著自己所持有的鎖而永遠的休眠,遲遲不歸還鎖等。在當前的api中,Thread.suspend、Thread.stop等方法都被Deprecated了,執行緒只能用interrupt中斷,而且不是立刻中斷,只是發了一個類似於訊號量的東西,通過修改了被呼叫執行緒的中斷狀態來告知那個執行緒, 說它被中斷了,至於什麼時候中斷,這個有系統判斷,會在一個合適的時候進行中斷處理。

/**
 * Created by Zero on 2017/8/17.
 */
public class ThreadTest1 {
    public static void main(String[] args) {
        NThread nThread = new NThread();
        System.out.println("interrupt執行前");
        nThread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        nThread.interrupt();
        System.out.println("interrupt執行後"
); } /** * 測試多執行緒的中斷機制 */ static class NThread extends Thread{ @Override public void run() { super.run(); while(true){ System.out.println("依然存活..."); try { Thread.sleep(1000); } catch
(InterruptedException e) { e.printStackTrace(); } } } } }

這裡寫圖片描述

在上面程式碼中,執行緒睡醒之後,呼叫thread執行緒的interrupt方法,catch到InteruptedException,設定標誌位。interrupt()方法相當於執行緒睡著的時候一盆涼水來吵醒它,執行緒表示不開心,並向你丟擲一個大大的異常以示不滿。

這裡寫圖片描述

  • 首先:執行緒中斷是一種協作機制,呼叫執行緒物件的interrupt方法並不一定就中斷了正在執行的執行緒,它只是要求執行緒自己在合適的時間中斷自己。
  • 其次:任務的方法必須是可中斷的,即方法必須丟擲InterruptedException。

由此可見,interrupt() 方法並不能立即中斷執行緒,該方法僅僅告訴執行緒外部已經有中斷請求,至於是否中斷還取決於執行緒自己。在Thread類中除了interrupt() 方法還有另外兩個非常相似的方法:interrupted 和 isInterrupted 方法,下面來對這幾個方法進行說明:

  • interrupt 此方法是例項方法,用於告訴此執行緒外部有中斷請求,並且將執行緒中的中斷標記設定為true,而不是立即中斷。
  • interrupted 此方法是類方法,用來判斷當前執行緒是否已經中斷。執行緒的中斷狀態由該方法清除。
  • isInterrupted 此方法是例項方法,用來判斷執行緒是否已經中斷。執行緒的中斷狀態不受該方法的影響。

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

/**
 * Created by Zero on 2017/8/17.
 */
public class ThreadTest1 {
    public static void main(String[] args) {
        NThread nThread = new NThread();
        nThread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("cancel執行前" + System.currentTimeMillis());
        nThread.cancel();
    }

    /**
     * 測試多執行緒的中斷機制
     */
    static class NThread extends Thread {

        private boolean isCancel;

        @Override
        public void run() {
            while (!isCancel) {
                System.out.println("依然存活...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("while結束" + System.currentTimeMillis());
        }

        public void cancel() {
            this.isCancel = true;
        }
    }
}

執行結果:

依然存活...
cancel執行前1502964413042
while結束1502964415042

機智的你,此刻一定發現執行前後相差2000毫秒,難道cancel()方法執行了2000毫秒?這純屬扯淡,裡面沒有任何耗時操作,就是一個賦值而已,其實是子執行緒的退出,前提條件是while迴圈結束,當且僅當cancel標示設定為true的瞬間立馬執行while的判斷,此時的時間差才可以忽略不計(1毫秒內),但是當我們呼叫cancel方法將isCancel 標記設定為true 時,while迴圈裡面有一個耗時操作(休眠5000毫秒),只有等待耗時操作執行完畢後才會去檢查這個標記,所以cancel方法和執行緒退出中間有時間間隔。

接下來,我們通過interrupt 和 isinterrupt 方法來中斷執行緒,程式碼如下:

/**
 * Created by Zero on 2017/8/17.
 */
public class ThreadTest1 {
    public static void main(String[] args) {
        NThread nThread = new NThread();
        nThread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("interrupt執行前"+System.currentTimeMillis());
        nThread.interrupt();
    }

    /**
     * 測試多執行緒的中斷機制
     */
    static class NThread extends Thread{

        @Override
        public void run() {
            while(!interrupted()){
                System.out.println("依然存活...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out.println("InterruptedException");
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("interrupt執行後"+System.currentTimeMillis());
        }
    }
}
依然存活...
interrupt執行前1502965915683
InterruptedException
interrupt執行後1502965915683

這次立馬中斷了,是因為在開啟子執行緒後,經過執行3000毫秒的休眠,執行緒執行了interrupt 方法,此時子執行緒的5000毫秒休眠還沒結束,就像上述所說的睡眠中被一盆冷水吵醒,很不開心的拋了異常,Thread.currentThread().interrupt() 改變了執行緒的標記狀態,在丟擲InterruptedException 的同時,執行緒的中斷標誌被清除了,再次執行while迴圈語句的時候,!interrupted() 此時是false,便不再執行while語句。

此處如果去掉Thread.currentThread().interrupt() ,執行緒便會無休止的執行下去,此處就不上程式碼了,就註釋掉這一行,執行就可以看到效果,經常看到一些程式碼在catch中不作任何處理,其實有時候這樣是很危險的,此處已經證明。

兩點建議:
1. 除非你知道執行緒的中斷策略,否則不應該中斷它。
2. 任務程式碼不該猜測中斷對執行執行緒的含義。遇到InterruptedException異常時,不應該將其捕獲後“吞掉”,而應該繼續向上層程式碼丟擲。

微信掃我,^_^