1. 程式人生 > >[轉載]Java 執行緒 5 種狀態下的執行緒中斷

[轉載]Java 執行緒 5 種狀態下的執行緒中斷

[轉載]Java併發之執行緒中斷

     前面的幾篇文章主要介紹了執行緒的一些最基本的概念,包括執行緒的間的衝突及其解決辦法,以及執行緒間的協作機制。本篇主要來學習下Java中對執行緒中斷機制的實現。在我們的程式中經常會有一些不達到目的不會退出的執行緒,例如:我們有一個下載程式執行緒,該執行緒在沒有下載成功之前是不會退出的,若此時使用者覺得下載速度慢,不想下載了,這時就需要用到我們的執行緒中斷機制了,告訴執行緒,你不要繼續執行了,準備好退出吧。當然,執行緒在不同的狀態下遇到中斷會產生不同的響應,有點會丟擲異常,有的則沒有變化,有的則會結束執行緒。本篇將從以下兩個方面來介紹Java中對執行緒中斷機制的具體實現:

  • Java中對執行緒中斷所提供的API支援
  • 執行緒在不同狀態下對於中斷所產生的反應

一、Java中對執行緒中斷所提供的API支援
     在以前的jdk版本中,我們使用stop方法中斷執行緒,但是現在的jdk版本中已經不再推薦使用該方法了,反而由以下三個方法完成對執行緒中斷的支援。

public boolean isInterrupted()

public void interrupt()

public static boolean interrupted() 

每個執行緒都一個狀態位用於標識當前執行緒物件是否是中斷狀態。isInterrupted是一個例項方法,主要用於判斷當前執行緒物件的中斷標誌位是否被標記了,如果被標記了則返回true表示當前已經被中斷,否則返回false。我們也可以看看它的實現原始碼:

public boolean isInterrupted() {
        return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);

底層呼叫的本地方法isInterrupted,傳入一個boolean型別的引數,用於指定呼叫該方法之後是否需要清除該執行緒物件的中斷標識位。從這裡我們也可以看出來,呼叫isInterrupted並不會清除執行緒物件的中斷標識位。

interrupt是一個例項方法,該方法用於設定當前執行緒物件的中斷標識位。

interrupted是一個靜態的方法,用於返回當前執行緒是否被中斷。

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

該方法用於判斷當前執行緒是否被中斷,並且該方法呼叫結束的時候會清空中斷標識位。下面我們看看執行緒所處不同狀態下對於中斷操作的反應。

二、執行緒在不同狀態下對於中斷所產生的反應
     執行緒一共6種狀態,分別是NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED(Thread類中有一個State列舉型別列舉了執行緒的所有狀態)。下面我們就將把執行緒分別置於上述的不同種狀態,然後看看我們的中斷操作對它們的影響。

1、NEW和TERMINATED
     執行緒的new狀態表示還未呼叫start方法,還未真正啟動。執行緒的terminated狀態表示執行緒已經執行終止。這兩個狀態下呼叫中斷方法來中斷執行緒的時候,Java認為毫無意義,所以並不會設定執行緒的中斷標識位,什麼事也不會發生。例如:

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

    Thread thread = new MyThread();
    System.out.println(thread.getState());

    thread.interrupt();

    System.out.println(thread.isInterrupted());
}

輸出結果如下:

run

terminated狀態:

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

     Thread thread = new MyThread();
     thread.start();

     thread.join();
     System.out.println(thread.getState());

     thread.interrupt();

    System.out.println(thread.isInterrupted());

}

輸出結果如下:

這裡寫圖片描述

從上述的兩個例子來看,對於處於new和terminated狀態的執行緒對於中斷是遮蔽的,也就是說中斷操作對這兩種狀態下的執行緒是無效的。

2、RUNNABLE
     如果執行緒處於執行狀態,那麼該執行緒的狀態就是RUNNABLE,但是不一定所有處於RUNNABLE狀態的執行緒都能獲得CPU執行,在某個時間段,只能由一個執行緒佔用CPU,那麼其餘的執行緒雖然狀態是RUNNABLE,但是都沒有處於執行狀態。而我們處於RUNNABLE狀態的執行緒在遭遇中斷操作的時候只會設定該執行緒的中斷標誌位,並不會讓執行緒實際中斷,想要發現本執行緒已經被要求中斷了則需要用程式去判斷。例如:

/*先定義一個執行緒類*/
public class MyThread extends Thread{

    @Override
    public void run(){
        while(true){
            //do something
        }
    }
}
/*main函式啟動執行緒*/
public static void main(String[] args) throws InterruptedException {

    Thread thread = new MyThread();
    thread.start();

    System.out.println(thread.getState());

    thread.interrupt();
    Thread.sleep(1000);//等到thread執行緒被中斷之後
    System.out.println(thread.isInterrupted());

    System.out.println(thread.getState());
}

我們定義的執行緒始終迴圈做一些事情,主執行緒啟動該執行緒並輸出該執行緒的狀態,然後呼叫中斷方法中斷該執行緒並再次輸出該執行緒的狀態。總的輸出結果如下:

這裡寫圖片描述

可以看到在我們啟動執行緒之後,執行緒狀態變為RUNNABLE,中斷之後輸出中斷標誌,顯然中斷位已經被標記,但是當我們再次輸出執行緒狀態的時候發現,執行緒仍然處於RUNNABLE狀態。很顯然,處於RUNNBALE狀態下的執行緒即便遇到中斷操作,也只會設定中斷標誌位並不會實際中斷執行緒執行。那麼問題是,既然不能直接中斷執行緒,我要中斷標誌有何用處?
這裡其實Java將這種權力交給了我們的程式,Java給我們提供了一箇中斷標誌位,我們的程式可以通過if判斷中斷標誌位是否被設定來中斷我們的程式而不是系統強制的中斷。例如:

/*修改MyThread類的run方法*/
public void run(){
    while(true){
        if (Thread.currentThread().isInterrupted()){
            System.out.println("exit MyThread");
            break;
        }
    }
}

執行緒一旦發現自己的中斷標誌為被設定了,立馬跳出死迴圈。這樣的設計好處就在於給了我們程式更大的靈活性。

3、BLOCKED
     當執行緒處於BLOCKED狀態說明該執行緒由於競爭某個物件的鎖失敗而被掛在了該物件的阻塞佇列上了。那麼此時發起中斷操作不會對該執行緒產生任何影響,依然只是設定中斷標誌位。例如:

/*自定義執行緒類*/
public class MyThread extends Thread{

    public synchronized static void doSomething(){
        while(true){
            //do something
        }
    }
    @Override
    public void run(){
        doSomething();
    }
}

這裡我們自定義了一個執行緒類,run方法中主要就做一件事情,呼叫一個有鎖的靜態方法,該方法內部是一個死迴圈(佔用該鎖讓其他執行緒阻塞)。

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

    Thread thread1 = new MyThread();
    thread1.start();

    Thread thread2 = new MyThread();
    thread2.start();

    Thread.sleep(1000);
    System.out.println(thread1.getState());
    System.out.println(thread2.getState());

    thread2.interrupt();
    System.out.println(thread2.isInterrupted());
    System.out.println(thread2.getState());
}

在我們的主執行緒中,我們定義了兩個執行緒並按照定義順序啟動他們,顯然thread1啟動後便佔用MyThread類鎖,此後thread2在獲取鎖的時候一定失敗,自然被阻塞在阻塞佇列上,而我們對thread2進行中斷,輸出結果如下:

這裡寫圖片描述

從輸出結果看來,thread2處於BLOCKED狀態,執行中斷操作之後,該執行緒仍然處於BLOCKED狀態,但是中斷標誌位卻已被修改。這種狀態下的執行緒和處於RUNNABLE狀態下的執行緒是類似的,給了我們程式更大的靈活性去判斷和處理中斷。

4、WAITING/TIMED_WAITING
     這兩種狀態本質上是同一種狀態,只不過TIMED_WAITING在等待一段時間後會自動釋放自己,而WAITING則是無限期等待,需要其他執行緒呼叫notify方法釋放自己。但是他們都是執行緒在執行的過程中由於缺少某些條件而被掛起在某個物件的等待佇列上。當這些執行緒遇到中斷操作的時候,會丟擲一個InterruptedException異常,並清空中斷標誌位。例如:

/*定義一個執行緒類*/
public class MyThread extends Thread{

    @Override
    public void run(){
        synchronized (this){
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("i am waiting but facing interruptexception now");
            }
        }
    }
}

我們定義了一個執行緒類,其中run方法讓當前執行緒阻塞到條件佇列上,並且針對InterruptedException 進行捕獲,如果遇到InterruptedException 異常則輸出一行資訊。

/*main函式啟動該執行緒*/
public static void main(String[] args) throws InterruptedException {

    Thread thread = new MyThread();
    thread.start();

    Thread.sleep(500);
    System.out.println(thread.getState());
    thread.interrupt();
    Thread.sleep(1000);
    System.out.println(thread.isInterrupted());
}

在main執行緒中我們啟動一個MyThread執行緒,然後對其進行中斷操作。

這裡寫圖片描述

從執行結果看,當前程thread啟動之後就被掛起到該執行緒物件的條件佇列上,然後我們呼叫interrupt方法對該執行緒進行中斷,輸出了我們在catch中的輸出語句,顯然是捕獲了InterruptedException異常,接著就看到該執行緒的中斷標誌位被清空。

綜上所述,我們分別介紹了不同種執行緒的不同狀態下對於中斷請求的反應。NEW和TERMINATED對於中斷操作幾乎是遮蔽的,RUNNABLE和BLOCKED類似,對於中斷操作只是設定中斷標誌位並沒有強制終止執行緒,對於執行緒的終止權利依然在程式手中。WAITING/TIMED_WAITING狀態下的執行緒對於中斷操作是敏感的,他們會丟擲異常並清空中斷標誌位。