Thread之八:interrupt中斷

Java中斷機制是一種協做機制,也就是說經過中斷並不能直接終止另外一個線程,它只是要求被中斷線程在合適的時機中斷本身,這須要被中斷的線程本身處理中斷。這比如是家裏的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎麼注意身體則徹底取決於本身。html

1、 中斷狀態的管理

通常說來,當可能阻塞的方法聲明中有拋出InterruptedException則暗示該方法是可中斷的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep、condition.await以及可中斷的通道上的 I/O 操做方法等,若是程序捕獲到這些可中斷的阻塞方法拋出的InterruptedException或檢測到中斷後,這些中斷信息該如何處理?通常有如下兩個通用原則:
若是遇到的是可中斷的阻塞方法拋出InterruptedException,能夠繼續向方法調用棧的上層拋出該異常,若是是檢測到中斷,則可清除中斷狀態並拋出InterruptedException,使當前方法也成爲一個可中斷的方法。
如有時候不太方便在方法上拋出InterruptedException,好比要實現的某個接口中的方法簽名上沒有throws InterruptedException,這時就能夠捕獲可中斷方法的InterruptedException並經過Thread.currentThread.interrupt()來從新設置中斷狀態。若是是檢測並清除了中斷狀態,亦是如此。
通常的代碼中,尤爲是做爲一個基礎類庫時,毫不應當吞掉中斷,即捕獲到InterruptedException後在catch裏什麼也不作,清除中斷狀態後又不重設中斷狀態也不拋出InterruptedException等。由於吞掉中斷狀態會致使方法調用棧的上層得不到這些信息。
固然,凡事總有例外的時候,當你徹底清楚本身的方法會被誰調用,而調用者也不會由於中斷被吞掉了而遇到麻煩,就能夠這麼作。
總得來講,就是要讓方法調用棧的上層獲知中斷的發生。假設你寫了一個類庫,類庫裏有個方法amethod,在amethod中檢測並清除了中斷狀態,而沒有拋出InterruptedException,做爲amethod的用戶來講,他並不知道里面的細節,若是用戶在調用amethod後也要使用中斷來作些事情,那麼在調用amethod以後他將永遠也檢測不到中斷了,由於中斷信息已經被amethod清除掉了。若是做爲用戶,遇到這樣有問題的類庫,又不能修改代碼,那該怎麼處理?只好在本身的類裏設置一個本身的中斷狀態,在調用interrupt方法的時候,同時設置該狀態,這實在是無路可走時才使用的方法。
注意:synchronized在獲鎖的過程當中是不能被中斷的,意思是說若是產生了死鎖,則不可能被中斷(請參考後面的測試例子)。與synchronized功能類似的reentrantLock.lock()方法也是同樣,它也不可中斷的,即若是發生死鎖,那麼reentrantLock.lock()方法沒法終止,若是調用時被阻塞,則它一直阻塞到它獲取到鎖爲止。可是若是調用帶超時的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那麼若是線程在等待時被中斷,將拋出一個InterruptedException異常,這是一個很是有用的特性,由於它容許程序打破死鎖。你也能夠調用reentrantLock.lockInterruptibly()方法,它就至關於一個超時設爲無限的tryLock方法。java

2、中斷線程(方法)

2.一、java.lang.Thread類提供了幾個方法來操做這個中斷狀態:

interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已。異步

即上面狀態圖中的:「等待Blocked」-->「鎖定Blocked」ide

一、interrupt()
interrupt方法用於中斷線程。調用該方法的線程的狀態爲將被置爲"中斷"狀態。
注意:線程中斷僅僅是置線程的中斷狀態位,不會中止線程。須要用戶本身去監視線程的狀態爲並作處理。支持線程中斷的方法(也就是線程中斷後會拋出interruptedException的方法)就是在監視線程的中斷狀態,一旦線程的中斷狀態被置爲「中斷狀態」,就會拋出中斷異常。
性能

二、interrupted()測試

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

該方法就是直接調用當前線程的isInterrupted(true)的方法。靜態方法interrupted會將當前線程的中斷狀態清除,但這個方法的命名極不直觀,很容易形成誤解,須要特別注意。
三、isInterrupted()this

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

該方法卻直接調用當前線程的native isInterrupted(false)的方法,不清除中斷狀態spa

所以interrupted()和isInterrupted()這兩個方法主要區別:這兩個方法最終都會調用同一個方法-----isInterrupted( Boolean 參數)(重載方法,是私有的native方法),只不過參數固定爲一個是true,一個是false;線程

因爲第二個區別主要體如今調用的方法的參數上,讓咱們來看一看這個參數是什麼含義:
先來看一看被調用的方法 isInterrupted(boolean arg)(Thread類中重載的方法)的定義:code

private native boolean isInterrupted( boolean flag);

這個本地方法,看不到源碼。做用是要清除狀態位。若是這個參數爲true,說明返回線程的狀態位後,要清掉原來的狀態位(恢復成原來狀況)。這個參數爲false,就是直接返回線程的狀態位

這兩個方法很好區分,只有當前線程才能清除本身的中斷位(對應interrupted()方法)

2.二、中斷方法

2.2.一、interrupt()

  interrupt()只是改變中斷狀態而已,interrupt()不會中斷(不是中止線程)一個正在運行的線程。這一方法實際上完成的是,給線程拋出一箇中斷信號, 這樣受阻線程就得以退出阻塞的狀態(不會中止線程。須要用戶本身去監視線程的狀態爲並作處理)。更確切的說,若是線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提前地終結被阻塞狀態。

  • 若是線程是阻塞(Object.wait, Thread.join和Thread.sleep)的,則線程會自動檢測中斷,拋出中斷異常(InterruptedException),給應用提供中斷相宜的處理。
  • 若是線程沒有被阻塞,則線程不會幫助咱們檢測中斷,須要咱們手動進行中斷檢測,在檢測到中斷後,應用能夠作相應的處理(同上)。如2.2.1.4中示例

或者能夠簡單總結一下中斷兩種狀況:

  • 一種是當線程處於阻塞狀態或者試圖執行一個阻塞操做時,咱們可使用實例方法interrupt()進行線程中斷,執行中斷操做後將會拋出interruptException異常(該異常必須捕捉沒法向外拋出)並將中斷狀態復位;
  • 另一種是當線程處於運行狀態時,咱們也可調用實例方法interrupt()進行線程中斷,但同時必須手動判斷中斷狀態,並編寫中斷線程的代碼(其實就是結束run方法體的代碼)

例如:
線程A在執行sleep,wait,join時,線程B調用線程A的interrupt方法,的確這一個時候A會有InterruptedException 異常拋出來。 但這實際上是在sleep,wait,join這些方法內部會不斷檢查中斷狀態的值,而本身拋出的InterruptedException。
若是線程A正在執行一些指定的操做時如賦值,for,while,if,調用方法等,都不會去檢查中斷狀態,因此線程A不會拋出 InterruptedException,而會一直執行着本身的操做。當線程A終於執行到wait(),sleep(),join()時,才立刻會拋出 InterruptedException.
若沒有調用sleep(),wait(),join()這些方法,即沒有在線程裏本身檢查中斷狀態本身拋出InterruptedException的話,那InterruptedException是不會被拋出來的。
注意1:當線程A執行到wait(),sleep(),join()時,拋出InterruptedException後,中斷狀態已經被系統復位了,線程A調用Thread.interrupted()返回的是false。

示例:interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已。

package com.dxz;

public class InterruptionInJava implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        Thread testThread = new Thread(new InterruptionInJava(), "InterruptionInJava");
        // start thread
        testThread.start();
        Thread.sleep(1000);
        // interrupt thread
        testThread.interrupt();
        
        System.out.println("main end");

    }

    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Yes,I am interruted,but I am still running");
            } else {
                System.out.println("not yet interrupted");
            }
        }
    }
}

 結果:

interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已,那麼,如何正確中斷?既然是隻能修改中斷狀態,那麼咱們應該針對中斷狀態作些什麼?

修改代碼,在狀態判斷中如上,添加一個return就ok了。但現實中,咱們可能須要作的更通用,不由又要發出天問,如何中斷線程?答案是添加一個開關

//...
if(Thread.currentThread().isInterrupted()){
                System.out.println("Yes,I am interruted,but I am still running");
                return;
//...

 

2.2.1.1. sleep() & interrupt()示例

線程A正在使用sleep()暫停着: Thread.sleep(100000);
若是要取消他的等待狀態,能夠在正在執行的線程裏(好比這裏是B)調用。

a.interrupt();
令線程A放棄睡眠操做,這裏a是線程A對應到的Thread實例
當在sleep中時 線程被調用interrupt()時,就立刻會放棄暫停的狀態.並拋出InterruptedException.丟出異常的,是A線程。

package com.dxz.interrupt;


public class ThreadDemo1A extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("線程a開始工做");
            sleep(30000);
        } catch (InterruptedException e) {
            System.out.println(this.isInterrupted());
        }
    }
}


package com.dxz.interrupt;

import java.util.concurrent.TimeUnit;

public class ThreadDemo1B {

    public static void main(String[] args) throws InterruptedException {
        ThreadDemo1A threada = new ThreadDemo1A();
        threada.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("開始中斷線程a");
        threada.interrupt();
    }
}

結果:

線程a開始工做
開始中斷線程a
false

2.2.1.2. wait() & interrupt()示例

線程A調用了wait()進入了等待狀態,也能夠用interrupt()取消。
不過這時候要當心鎖定的問題.線程在進入等待區,會把鎖定解除,當對等待中的線程調用interrupt()時,會先從新獲取鎖定,再拋出異常。在獲取鎖定以前,是沒法拋出異常的。

package com.dxz.interrupt;

public class ThreadDemo2A extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("線程a開始工做");
            synchronized (this) {
                wait(30000);
            }
        } catch (InterruptedException e) {
            System.out.println(this.isInterrupted());
        }
    }
}

package com.dxz.interrupt;

import java.util.concurrent.TimeUnit;

public class ThreadDemo2B {

    public static void main(String[] args) throws InterruptedException {
        ThreadDemo2A threada = new ThreadDemo2A();
        threada.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("開始中斷線程a");
        threada.interrupt();
    }
}

結果:

線程a開始工做
開始中斷線程a
false

2.2.1.3. join() & interrupt()示例

當線程以join()等待其餘線程結束時,當它被調用interrupt(),它與sleep()時同樣, 會立刻跳到catch塊裏。注意,是對誰調用interrupt()方法,必定是調用被阻塞線程的interrupt方法.如在線程a中調用來線程t.join(),
則a會等t執行完後在執行t.join後的代碼,當在線程b中調用a.interrupt()方法,則會拋出InterruptedException,固然線程t的join()也就被取消了。線程t還會繼續運行下去。

package com.dxz.interrupt;

public class T extends Thread {

    public T(String s) {
        super(s);
    }
    
    @Override
    public void run() {
        System.out.println("線程t開始進入死循環");
        for(;;);
    }
}

package com.dxz.interrupt;


public class ThreadDemo3A extends Thread {

    public ThreadDemo3A(String s) {
        super(s);
    }
    @Override
    public void run() {
        try {
            System.out.println("線程a等待線程t執行");
            T t = new T("demo3t");
            t.start();
            t.join();
            System.out.println("線程a中的線程t已經結束");
        } catch (InterruptedException e) {
            System.out.println(this.isInterrupted());
        }
    }

}
package com.dxz.interrupt;

import java.util.concurrent.TimeUnit;

public class ThreadDemo3B  {

    public static void main(String[] args) {
        ThreadDemo3A threada = new ThreadDemo3A("demo3a");
        threada.start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("開始中斷線程a");
        threada.interrupt();
        System.out.println("--------");
    }
    
}

結果:

線程a等待線程t執行
線程t開始進入死循環
開始中斷線程a
--------
false

能夠看到T線程在主線程結束了,還在運行。

2.2.1.4. 處於非阻塞狀態的線程須要咱們手動進行中斷檢測並結束程序--示例

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 判斷當前線程是否被中斷
                    if (this.isInterrupted()) {
                        System.out.println("線程中斷");
                        break;
                    }
                }

                System.out.println("已跳出循環,線程中斷!");
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();
    }
}

結果:

線程中斷
已跳出循環,線程中斷!

2.2.二、類庫中的有些類的方法也可能會調用中斷

此外,類庫中的有些類的方法也可能會調用中斷,如FutureTask中的cancel方法,若是傳入的參數爲true,它將會在正在運行異步任務的線程上調用interrupt方法,若是正在執行的異步任務中的代碼沒有對中斷作出響應,那麼cancel方法中的參數將不會起到什麼效果;又如ThreadPoolExecutor中的shutdownNow方法會遍歷線程池中的工做線程並調用線程的interrupt方法來中斷線程,因此若是工做線程中正在執行的任務沒有對中斷作出響應,任務將一直執行直到正常結束。

3、線程被中斷的檢測(判斷)

判斷某個線程是否已被髮送過中斷請求,請使用Thread.currentThread().isInterrupted()方法(由於它將線程中斷標示位設置爲true後,不會馬上清除中斷標示位,即不會將中斷標設置爲false),而不要使用thread.interrupted()(該方法調用後會將中斷標示位清除,即從新設置爲false)方法來判斷,下面是線程在循環中時的中斷方式:
while(!Thread.currentThread().isInterrupted() && more work to do){
do more work
}

4、處理時機

顯然,做爲一種協做機制,不會強求被中斷線程必定要在某個點進行處理。實際上,被中斷線程只需在合適的時候處理便可,若是沒有合適的時間點,甚至能夠不處理,這時候在任務處理層面,就跟沒有調用中斷方法同樣。「合適的時候」與線程正在處理的業務邏輯緊密相關,例如,每次迭代的時候,進入一個可能阻塞且沒法中斷的方法以前等,但多半不會出如今某個臨界區更新另外一個對象狀態的時候,由於這可能會致使對象處於不一致狀態。
處理時機決定着程序的效率與中斷響應的靈敏性。頻繁的檢查中斷狀態可能會使程序執行效率降低,相反,檢查的較少可能使中斷請求得不到及時響應。若是發出中斷請求以後,被中斷的線程繼續執行一段時間不會給系統帶來災難,那麼就能夠將中斷處理放到方便檢查中斷,同時又能從必定程度上保證響應靈敏度的地方。當程序的性能指標比較關鍵時,可能須要創建一個測試模型來分析最佳的中斷檢測點,以平衡性能和響應靈敏性。
處理方式

5、 中斷的響應

程序裏發現中斷後該怎麼響應?這就得視實際狀況而定了。有些程序可能一檢測到中斷就立馬將線程終止,有些多是退出當前執行的任務,繼續執行下一個任務……做爲一種協做機制,這要與中斷方協商好,當調用interrupt會發生些什麼都是事先知道的,如作一些事務回滾操做,一些清理工做,一些補償操做等。若不肯定調用某個線程的interrupt後該線程會作出什麼樣的響應,那就不該當中斷該線程。

六. Thread.interrupt VS Thread.stop

Thread.stop方法已經不推薦使用了。而在某些方面Thread.stop與中斷機制有着類似之處。如當線程在等待內置鎖或IO時,stop跟interrupt同樣,不會停止這些操做;當catch住stop致使的異常時,程序也能夠繼續執行,雖然stop本意是要中止線程,這麼作會讓程序行爲變得更加混亂。
那麼它們的區別在哪裏?最重要的就是中斷須要程序本身去檢測而後作相應的處理,而Thread.stop會直接在代碼執行過程當中拋出ThreadDeath錯誤,這是一個java.lang.Error的子類。

Thread之九:stop

相關文章
相關標籤/搜索