1. 程式人生 > >Java執行緒的終止——interrupt

Java執行緒的終止——interrupt

取消/關閉的場景

我們知道,通過執行緒的start方法啟動一個執行緒後,執行緒開始執行run方法,run方法執行結束後執行緒退出,那為什麼還需要結束一個執行緒呢?有多種情況,比如說:
很多執行緒的執行模式是死迴圈,比如在生產者/消費者模式中,消費者主體就是一個死迴圈,它不停的從佇列中接受任務,執行任務,在停止程式時,我們需要一種”優雅”的方法以關閉該執行緒。
在一些圖形使用者介面程式中,執行緒是使用者啟動的,完成一些任務,比如從遠端伺服器上下載一個檔案,在下載過程中,使用者可能會希望取消該任務。
在一些場景中,比如從第三方伺服器查詢一個結果,我們希望在限定的時間內得到結果,如果得不到,我們會希望取消該任務。
有時,我們會啟動多個執行緒做同一件事,比如類似搶火車票,我們可能會讓多個好友幫忙從多個渠道買火車票,只要有一個渠道買到了,我們會通知取消其他渠道。

取消/關閉的機制
Java的Thread類定義瞭如下方法:
public final void stop()

這個方法看上去就可以停止執行緒,但這個方法被標記為了過時,簡單的說,我們不應該使用它,可以忽略它。

在Java中,停止一個執行緒的主要機制是中斷,中斷並不是強迫終止一個執行緒,它是一種協作機制,是給執行緒傳遞一個取消訊號,但是由執行緒來決定如何以及何時退出,本節我們主要就是來理解Java的中斷機制。

Thread類定義瞭如下關於中斷的方法:
public boolean isInterrupted()
public void interrupt()
public static boolean interrupted

()

這三個方法名字類似,比較容易混淆,我們解釋一下。isInterrupted()和interrupt()是例項方法,呼叫它們需要通過執行緒物件,interrupted()是靜態方法,實際會呼叫Thread.currentThread()操作當前執行緒。

每個執行緒都有一個標誌位,表示該執行緒是否被中斷了。
isInterrupted:就是返回對應執行緒的中斷標誌位是否為true。
interrupted:返回當前執行緒的中斷標誌位是否為true,但它還有一個重要的副作用,就是清空中斷標誌位,也就是說,連續兩次呼叫interrupted(),第一次返回的結果為true,第二次一般就是false (除非同時又發生了一次中斷)。
interrupt:表示中斷對應的執行緒,中斷具體意味著什麼呢?下面我們進一步來說明。

執行緒對中斷的反應
interrupt()對執行緒的影響與執行緒的狀態和在進行的IO操作有關,我們先主要考慮執行緒的狀態:
RUNNABLE:執行緒在執行或具備執行條件只是在等待作業系統排程
WAITING/TIMED_WAITING:執行緒在等待某個條件或超時
BLOCKED:執行緒在等待鎖,試圖進入同步塊
NEW/TERMINATED:執行緒還未啟動或已結束

RUNNABLE
如果執行緒在執行中,且沒有執行IO操作,interrupt()只是會設定執行緒的中斷標誌位,沒有任何其它作用。執行緒應該在執行過程中合適的位置檢查中斷標誌位,比如說,如果主體程式碼是一個迴圈,可以在迴圈開始處進行檢查,如下所示:

public class InterruptRunnableDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // ... 單次迴圈程式碼
        }
        System.out.println("done ");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptRunnableDemo();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

WAITING/TIMED_WAITING
執行緒執行如下方法會進入WAITING狀態:
public final void join() throws InterruptedException
public final void wait() throws InterruptedException

執行如下方法會進入TIMED_WAITING狀態:
public final native void wait(long timeout) throws InterruptedException;
public static native void sleep(long millis) throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException

在這些狀態時,對執行緒物件呼叫interrupt()會使得該執行緒丟擲InterruptedException,需要注意的是,丟擲異常後,中斷標誌位會被清空,而不是被設定。比如說,執行如下程式碼:

Thread t = new Thread (){
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println(isInterrupted());
        }
    }        
};
t.start();
try {
    Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();

程式的輸出為false。

InterruptedException是一個受檢異常,執行緒必須進行處理。我們在異常處理中介紹過,處理異常的基本思路是,如果你知道怎麼處理,就進行處理,如果不知道,就應該向上傳遞,通常情況下,你不應該做的是,捕獲異常然後忽略。

捕獲到InterruptedException,通常表示希望結束該執行緒,執行緒大概有兩種處理方式:
向上傳遞該異常,這使得該方法也變成了一個可中斷的方法,需要呼叫者進行處理。
有些情況,不能向上傳遞異常,比如Thread的run方法,它的宣告是固定的,不能丟擲任何受檢異常,這時,應該捕獲異常,進行合適的清理操作,清理後,一般應該呼叫Thread的interrupt方法設定中斷標誌位,使得其他程式碼有辦法知道它發生了中斷。

第一種方式的示例程式碼如下:

public void interruptibleMethod() throws InterruptedException{
    // ... 包含wait, join 或 sleep 方法
    Thread.sleep(1000);
}

第二種方式的示例程式碼如下:

public class InterruptWaitingDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 模擬任務程式碼
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // ... 清理操作
                // 重設中斷標誌位
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(isInterrupted());
    }

    public static void main(String[] args) {
        InterruptWaitingDemo thread = new InterruptWaitingDemo();
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        thread.interrupt();
    }
}

BLOCKED
如果執行緒在等待鎖,對執行緒物件呼叫interrupt()只是會設定執行緒的中斷標誌位,執行緒依然會處於BLOCKED狀態,也就是說,interrupt()並不能使一個在等待鎖的執行緒真正”中斷”。我們看段程式碼:

public class InterruptSynchronizedDemo {
    private static Object lock = new Object();

    private static class A extends Thread {
        @Override
        public void run() {
            synchronized (lock) {
                while (!Thread.currentThread().isInterrupted()) {
                }
            }
            System.out.println("exit");
        }
    }

    public static void test() throws InterruptedException {
        synchronized (lock) {
            A a = new A();
            a.start();
            Thread.sleep(1000);

            a.interrupt();
            a.join();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        test();
    }
}

test方法在持有鎖lock的情況下啟動執行緒a,而執行緒a也去嘗試獲得鎖lock,所以會進入鎖等待佇列,隨後test呼叫執行緒a的interrupt方法並等待執行緒執行緒a結束,執行緒a會結束嗎?不會,interrupt方法只會設定執行緒的中斷標誌,而並不會使它從鎖等待佇列中出來。

我們稍微修改下程式碼,去掉test方法中的最後一行a.join,即變為:

public static void test() throws InterruptedException {
    synchronized (lock) {
        A a = new A();
        a.start();
        Thread.sleep(1000);

        a.interrupt();
    }
}

這時,程式就會退出。為什麼呢?因為主執行緒不再等待執行緒a結束,釋放鎖lock後,執行緒a會獲得鎖,然後檢測到發生了中斷,所以會退出。

在使用synchronized關鍵字獲取鎖的過程中不響應中斷請求,這是synchronized的侷限性。如果這對程式是一個問題,應該使用顯式鎖,後面章節我們會介紹顯式鎖Lock介面,它支援以響應中斷的方式獲取鎖。

NEW/TERMINATE
如果執行緒尚未啟動(NEW),或者已經結束(TERMINATED),則呼叫interrupt()對它沒有任何效果,中斷標誌位也不會被設定。比如說,以下程式碼的輸出都是false。

public class InterruptNotAliveDemo {
    private static class A extends Thread {
        @Override
        public void run() {
        }
    }

    public static void test() throws InterruptedException {
        A a = new A();
        a.interrupt();
        System.out.println(a.isInterrupted());

        a.start();
        Thread.sleep(100);
        a.interrupt();
        System.out.println(a.isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        test();
    }
}

IO操作
如果執行緒在等待IO操作,尤其是網路IO,則會有一些特殊的處理,我們沒有介紹過網路,這裡只是簡單介紹下。
如果IO通道是可中斷的,即實現了InterruptibleChannel介面,則執行緒的中斷標誌位會被設定,同時,執行緒會收到異常ClosedByInterruptException。
如果執行緒阻塞於Selector呼叫,則執行緒的中斷標誌位會被設定,同時,阻塞的呼叫會立即返回。

我們重點介紹另一種情況,InputStream的read呼叫,該操作是不可中斷的,如果流中沒有資料,read會阻塞 (但執行緒狀態依然是RUNNABLE),且不響應interrupt(),與synchronized類似,呼叫interrupt()只會設定執行緒的中斷標誌,而不會真正”中斷”它,我們看段程式碼。

public class InterruptReadDemo {
    private static class A extends Thread {
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                try {
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }    
            }
            System.out.println("exit");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        A t = new A();
        t.start();
        Thread.sleep(100);

        t.interrupt();
    }
}

執行緒t啟動後呼叫System.in.read()從標準輸入讀入一個字元,不要輸入任何字元,我們會看到,呼叫interrupt()不會中斷read(),執行緒會一直執行。

不過,有一個辦法可以中斷read()呼叫,那就是呼叫流的close方法,我們將程式碼改為:

public class InterruptReadDemo {
    private static class A extends Thread {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("exit");
        }

        public void cancel() {
            try {
                System.in.close();
            } catch (IOException e) {
            }
            interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        A t = new A();
        t.start();
        Thread.sleep(100);

        t.cancel();
    }
}

我們給執行緒定義了一個cancel方法,在該方法中,呼叫了流的close方法,同時呼叫了interrupt方法,這次,程式會輸出:

-1
exit

也就是說,呼叫close方法後,read方法會返回,返回值為-1,表示流結束。

如何正確地取消/關閉執行緒

有三種方法可以使終止執行緒。

1.  使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。 

2.  使用stop方法強行終止執行緒(這個方法不推薦使用,因為stop和suspend、resume一樣,也可能發生不可預料的結果)。 

3.  使用interrupt方法中斷執行緒。

以上,我們可以看出,interrupt方法不一定會真正”中斷”執行緒,它只是一種協作機制,如果不明白執行緒在做什麼,不應該貿然的呼叫執行緒的interrupt方法,以為這樣就能取消執行緒。

對於以執行緒提供服務的程式模組而言,它應該封裝取消/關閉操作,提供單獨的取消/關閉方法給呼叫者,類似於InterruptReadDemo中演示的cancel方法,外部呼叫者應該呼叫這些方法而不是直接呼叫interrupt。

Java併發庫的一些程式碼就提供了單獨的取消/關閉方法,比如說,Future介面提供瞭如下方法以取消任務:
boolean cancel(boolean mayInterruptIfRunning);

再比如,ExecutorService提供瞭如下兩個關閉方法:
void shutdown();
List shutdownNow();

Future和ExecutorService的API文件對這些方法都進行了詳細說明,這是我們應該學習的方式。關於這兩個介面,我們後續章節介紹。

小結
本節主要介紹了在Java中如何取消/關閉執行緒,主要依賴的技術是中斷,但它是一種協作機制,不會強迫終止執行緒,我們介紹了執行緒在不同狀態和IO操作時對中斷的反應,作為執行緒的實現者,應該提供明確的取消/關閉方法,並用文件描述清楚其行為,作為執行緒的呼叫者,應該使用其取消/關閉方法,而不是貿然呼叫interrupt

相關推薦

Java 執行中斷(interrupt)與阻塞 (park)的區別

    很多Java開發人員(包括我),尤其是剛進入軟體行業的新手,認為Java設定執行緒中斷就是表示執行緒停止了,不往前執行了, Thread.currentThread().interrupt()    其實不是這樣的,執行緒中斷只是一個狀態而已,true表示已

JAVA 執行中斷interrupt()

執行緒中斷有三種方法: 1、stop(),暴力中斷,會導致事物不完整,已被廢棄 2、flag標記法,用標記變數做迴圈條件,外部執行緒改變flag的值來達到中斷執行緒的目的 3、interrupt()和isInterrupted()配合使用,本質上和方法2一樣 重點看方法3

Java執行interrupt()和執行終止方式

1. interrupt()說明 在介紹終止執行緒的方式之前,有必要先對interrupt()進行了解。 關於interrupt(),java的djk文件描述如下:http://docs.oracle.com/javase/7/docs/api/ Interrupts this thread

Java執行終止——interrupt

取消/關閉的場景 我們知道,通過執行緒的start方法啟動一個執行緒後,執行緒開始執行run方法,run方法執行結束後執行緒退出,那為什麼還需要結束一個執行緒呢?有多種情況,比如說: 很多執行緒的執行模式是死迴圈,比如在生產者/消費者模式中,消費者主體就是一

java執行中的interrupt,isInterrupt,interrupted方法以及如何終止執行(一)

在java的執行緒Thread類中有三個方法,比較容易混淆,在這裡解釋一下(1)interrupt:置執行緒的中斷狀態(2)isInterrupt:執行緒是否中斷(3)interrupted:返回執行緒的上次的中斷狀態,並清除中斷狀態舉個例子: 用法:   class 

JAVAinterrupt()方法和執行終止的方式

1 // Demo3.java的原始碼 2 class MyThread extends Thread { 3 4 private volatile boolean flag= true; 5 public void stopTask() { 6 flag =

Java執行基礎(十)interrupt()和執行終止方式

Java 多執行緒基礎(十)interrupt()和執行緒終止方式 一、interrupt() 介紹 interrupt() 定義在 Thread 類中,作用是中斷本執行緒。 本執行緒中斷自己是被允許的;其它執行緒呼叫本執行緒的 interrupt() 方法時,會通過 checkAccess() 檢查許可權。

Java執行Thread之interrupt中斷解析

轉載請標明出處: http://blog.csdn.net/hesong1120/article/details/79164445 本文出自:hesong的專欄 這一篇我們說說Java執行緒Thread的interrupt中斷機制。 interrupt之中斷狀態

java執行學習(三):執行中斷 interrupt() 方法的使用

上一章節中,我們對執行緒終止stop方法進行了講解,stop終止執行緒的方法已經被丟棄,原因是執行緒的終止太暴力,會導致不必要的資料錯誤,所以stop方法在不自信的情況下,慎用慎用。。。。同時,也提供了較為完善的終止方案了。 本節就來學習執行緒中斷 interrupt() 方法的使用: 一、

java執行學習(二): 終止執行講解:Stop()方法(後附如何正確終止執行)

本章來學習Java的stop執行緒終止方法; 老規矩,先看原始碼: @Deprecated public final void stop() { SecurityManager var1 = System.getSecurityManager(); if (var1 != n

java 執行 interrupt 詳解

這篇文章可以讓你清晰的瞭解:       interrupt() 方法的作用是什麼?       對一個執行緒呼叫 interrupt() 後,它一定會中斷麼?       如果不能保證中斷,那 interrupt() 的真正作用是什麼呢?      

理解java執行的中斷(interrupt)

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

java執行中的interrupt,isInterrupt,interrupted方法

在java的執行緒Thread類中有三個方法,比較容易混淆,在這裡解釋一下 (1)interrupt:置執行緒的中斷狀態 (2)isInterrupt:執行緒是否中斷 (3)interrupted:返回執行緒的上次的中斷狀態,並清除中斷狀態 舉個例子: 用法:  clas

java執行阻塞喚醒、interrupt測試

執行緒阻塞可以採用Object.wait()、Object.notify()來控制執行緒的阻塞喚醒。 另一種方式是呼叫Unsafe.park()、Unsafe.unpark()。 在主動呼叫執行緒interrupt方法之後,目標執行緒如果正在block狀態就會被喚醒,

停止Java執行,小心interrupt()方法

程式是很簡易的。然而,在程式設計人員面前,多執行緒呈現出了一組新的難題,如果沒有被恰當的解決,將導致意外的行為以及細微的、難以發現的錯誤。  在本篇文章中,我們針對這些難題之一:如何中斷一個正在執行的執行緒。  背景     中斷(Interrupt)一個執行緒意味著在該

Java執行操作

轉載請標明出處:http://blog.csdn.net/wu_wxc/article/details/51764557 本文出自【吳孝城的CSDN部落格】 Java中執行緒的實現有兩種方法 繼承Thread類 實現Runnable介面 執行緒的狀態 新

JAVA執行間協作wait、notify、notifyAll、sleep用途

在上節中,介紹了java多執行緒中同步鎖的概念,synchronized方法和synchronized程式碼塊都是為了解決執行緒併發的問題,同一時間允許一個執行緒訪問當前類或者物件。如果涉及到執行緒間的協作通訊,就需要用到wait、notify、notifyAll方法,這三個方法是Object的

Java執行詳解

這篇文章計劃講一下整個Java執行緒的生命週期。 要了解一個執行緒,首先要從它的建立說起,然後是執行緒的執行以及執行緒與執行緒之間的互動,最後是執行緒的銷燬。 一、執行緒的建立 執行緒的建立有四種方式: 1)繼承Thread類建立執行緒 2)實現Runnable介面建立執行緒 3)使用C

Java執行-同步和非同步的區別

1.                                         &nb

Java執行模型總結

1. 計算機系統 使用快取記憶體來作為記憶體與處理器之間的緩衝,將運算需要用到的資料複製到快取中,讓計算能快速進行;當運算結束後再從快取同步回記憶體之中,這樣處理器就無需等待緩慢的記憶體讀寫了。 快取一致性:多處理器系統中,因為共享同一主記憶體,當多個處理器的運算任務都設計到同一塊記憶體區域