JAVA interrupte中斷線程的真正用途

 Java線程之中,一個線程的生命週期分爲:初始、就緒、運行、阻塞以及結束。固然,其中也能夠有四種狀態,初始、就緒、運行以及結束。html

         通常而言,可能有三種緣由引發阻塞:等待阻塞、同步阻塞以及其餘阻塞(睡眠、join或者IO阻塞);對於Java而言,等待阻塞是調用wait方法產生的,同步阻塞則是由同步塊(synchronized)產生的,睡眠阻塞是由sleep產生的,join阻塞是由join方法產生的。java

         言歸正傳,要中斷一個Java線程,可調用線程類(Thread)對象的實例方法:interrupte();然而interrupte()方法並不會當即執行中斷操做;具體而言,這個方法只會給線程設置一個爲true的中斷標誌(中斷標誌只是一個布爾類型的變量),而設置以後,則根據線程當前的狀態進行不一樣的後續操做。若是,線程的當前狀態處於非阻塞狀態,那麼僅僅是線程的中斷標誌被修改成true而已;若是線程的當前狀態處於阻塞狀態,那麼在將中斷標誌設置爲true後,還會有以下三種狀況之一的操做:程序員

 

  • 若是是wait、sleep以及jion三個方法引發的阻塞,那麼會將線程的中斷標誌從新設置爲false,並拋出一個InterruptedException;
  • 若是是java.nio.channels.InterruptibleChannel進行的io操做引發的阻塞,則會對線程拋出一個ClosedByInterruptedException;(待驗證)
  • 若是是輪詢(java.nio.channels.Selectors)引發的線程阻塞,則當即返回,不會拋出異常。(待驗證)
    若是在中斷時,線程正處於非阻塞狀態,則將中斷標誌修改成true,而在此基礎上,一旦進入阻塞狀態,則按照阻塞狀態的狀況來進行處理;例如, 一個線程在運行狀態中,其中斷標誌被設置爲true,則此後,一旦線程調用了wait、join、sleep方法中的一種,立馬拋出一個InterruptedException,且中斷標誌被清除,從新設置爲false。
    經過上面的分析,咱們能夠總結, 調用線程類的interrupted方法,其本質只是設置該線程的中斷標誌,將中斷標誌設置爲true,並根據線程狀態決定是否拋出異常。所以,經過interrupted方法真正實現線程的中斷原理是: 開發人員根據中斷標誌的具體值,來決定如何退出線程。
   一個簡單的實現方式以下:
  1. public void run() {  
  2.             try {  
  3.                 while (true){  
  4.                     Thread.sleep(1000l);//阻塞狀態,線程被調用了interrupte()方法,清除中斷標誌,拋出InterruptedException  
  5.                     //dosomething  
  6.                     boolean isIn = this.isInterrupted();  
  7.                     //運行狀態,線程被調用了interrupte()方法,中斷標誌被設置爲true  
  8.                     //非阻塞狀態中進行中斷線程操做  
  9.                     if(isIn) break;//退出循環,中斷進程  
  10.                 }  
  11.             }catch (InterruptedException e){//阻塞狀態中進行中斷線程操做  
  12.                 boolean isIn = this.isInterrupted();//退出阻塞狀態,且中斷標誌被清除,從新設置爲false,因此此處的isIn爲false  
  13.                 return;//退出run方法,中斷進程  
  14.             }  
  15.         }  
        分別考慮了阻塞狀態中進行中斷線程和非阻塞狀態中中斷線程的處理方式。

 

        最後,說明一下interrupte方法的調用,該方法可在須要中斷的線程自己中調用,也可在其餘線程中調用須要中斷的線程對象的該方法。編程

(一).關於interrupt()
    interrupt()並不直接中斷線程,而是設定一箇中斷標識,而後由程序進行中斷檢查,肯定是否中斷。
    1. sleep() & interrupt()
    線程A正在使用sleep()暫停着: Thread.sleep(100000);
    若是要取消他的等待狀態,能夠在正在執行的線程裏(好比這裏是B)調用a.interrupt();
    令線程A放棄睡眠操做,這裏a是線程A對應到的Thread實例執行interrupt()時,並不須要獲取Thread實例的鎖定.任何線程在任什麼時候刻,均可以調用其餘線程interrupt().當sleep中的線程被調用interrupt()時,就會放棄暫停的狀態.並拋出InterruptedException.丟出異常的,是A線程.
2. wait() & interrupt()
    線程A調用了wait()進入了等待狀態,也能夠用interrupt()取消.
    不過這時候要當心鎖定的問題.線程在進入等待區,會把鎖定解除,當對等待中的線程調用interrupt()時(注意是等待的線程調用其本身的interrupt()),會先從新獲取鎖定,再拋出異常.在獲取鎖定以前,是沒法拋出異常的.
3. join() & interrupt()
    當線程以join()等待其餘線程結束時,同樣可使用interrupt()取消之.由於調用join()不須要獲取鎖定,故與sleep()時同樣,會立刻跳到catch塊裏. 注意是隨調用interrupt()方法,必定是阻塞的線程來調用其本身的interrupt方法.如在線程a中調用來線程t.join().則a會等t執行完後在執行t.join後的代碼,當在線程b中調用來a.interrupt()方法,則會拋出InterruptedException
4. interrupt()只是改變中斷狀態而已
    interrupt()不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,若是線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提前地終結被阻塞狀態。
    若是線程沒有被阻塞,這時調用interrupt()將不起做用;不然,線程就將獲得異常(該線程必須事先預備好處理此情況),接着逃離阻塞狀態。
    線程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是不會被拋出來的.   
 
(二)Java線程中斷的本質和編程原則
    在歷史上,Java試圖提供過搶佔式限制中斷,但問題多多,例如前文介紹的已被廢棄的Thread.stop、Thread.suspend和 Thread.resume等。另外一方面,出於Java應用代碼的健壯性的考慮,下降了編程門檻,減小不清楚底層機制的程序員無心破壞系統的機率。
    現在,Java的線程調度不提供搶佔式中斷,而採用協做式的中斷。其實,協做式的中斷,原理很簡單,就是輪詢某個表示中斷的標記,咱們在任何普通代碼的中均可以實現。 例以下面的代碼:
    volatile bool isInterrupted;
    //…
    while(!isInterrupted) {
        compute();
    }
    可是,上述的代碼問題也很明顯。當compute執行時間比較長時,中斷沒法及時被響應。另外一方面,利用輪詢檢查標誌變量的方式,想要中斷wait和sleep等線程阻塞操做也一籌莫展。
    若是仍然利用上面的思路,要想讓中斷及時被響應,必須在虛擬機底層進行線程調度的對標記變量進行檢查。是的,JVM中確實是這樣作的。下面摘自java.lang.Thread的源代碼:
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
       //…
        private native boolean isInterrupted(boolean ClearInterrupted);
    能夠發現,isInterrupted被聲明爲native方法,取決於JVM底層的實現。
    實際上,JVM內部確實爲每一個線程維護了一箇中斷標記。但應用程序不能直接訪問這個中斷變量,必須經過下面幾個方法進行操做:
    public class Thread {
        //設置中斷標記
        public void interrupt() { ... } 
        //獲取中斷標記的值
        public boolean isInterrupted() { ... }
        //清除中斷標記,並返回上一次中斷標記的值
        public static boolean interrupted() { ... }  
        ...
    }
    一般狀況下,調用線程的interrupt方法,並不能當即引起中斷,只是設置了JVM內部的中斷標記。所以,經過檢查中斷標記,應用程序能夠作一些特殊操做,也能夠徹底忽略中斷。
    你可能想,若是JVM只提供了這種簡陋的中斷機制,那和應用程序本身定義中斷變量並輪詢的方法相比,基本也沒有什麼優點。
    JVM內部中斷變量的主要優點,就是對於某些狀況,提供了模擬自動「中斷陷入」的機制。
    在執行涉及線程調度的阻塞調用時(例如wait、sleep和join),若是發生中斷,被阻塞線程會「儘量快的」拋出InterruptedException。所以,咱們就能夠用下面的代碼框架來處理線程阻塞中斷:
    try {
        //wait、sleep或join
       }
    catch(InterruptedException e) {
        //某些中斷處理工做 
      }
    所謂「儘量快」,我猜想JVM就是在線程調度調度的間隙檢查中斷變量,速度取決於JVM的實現和硬件的性能。   
    然而,對於某些線程阻塞操做,JVM並不會自動拋出InterruptedException異常。例如,某些I/O操做和內部鎖操做。對於這類操做,能夠用其餘方式模擬中斷:
    1)java.io中的異步socket I/O
    讀寫socket的時候,InputStream和OutputStream的read和write方法會阻塞等待,但不會響應java中斷。不過,調用Socket的close方法後,被阻塞線程會拋出SocketException異常。
    2)利用Selector實現的異步I/O
    若是線程被阻塞於Selector.select(在java.nio.channels中),調用wakeup方法會引發ClosedSelectorException異常。
    3)鎖獲取
    若是線程在等待獲取一個內部鎖,咱們將沒法中斷它。可是,利用Lock類的lockInterruptibly方法,咱們能夠在等待鎖的同時,提供中斷能力。
    另外,在任務與線程分離的框架中,任務一般並不知道自身會被哪一個線程調用,也就不知道調用線程處理中斷的策略。因此,在任務設置了線程中斷標記後,並不能確保任務會被取消。所以,有如下兩條編程原則:
     1)除非你知道線程的中斷策略,不然不該該中斷它。
        這條原則告訴咱們,不該該直接調用Executer之類框架中線程的interrupt方法,應該利用諸如Future.cancel的方法來取消任務。
    2)任務代碼不應猜想中斷對執行線程的含義。
        這條原則告訴咱們,通常代碼遇在到InterruptedException異常時,不該該將其捕獲後「吞掉」,而應該繼續向上層代碼拋出。
    總之,Java中的非搶佔式中斷機制,要求咱們必須改變傳統的搶佔式中斷思路,在理解其本質的基礎上,採用相應的原則和模式來編程。
(三) interrupt() 與 cancel()的區別
   二者實際上都是中斷線程,可是後者更安全、有條理和高效,其緣由跟推薦使用 Executor而不直接使用Thread類是一致的。因此結合上面講到的原則,咱們應儘可能採用cancel()方法,調用線程管理器ExecutorService接口的 submit ( Runnable  task)  方法會返回一個Future<?>對象,而後調用Future.cancel()的方法來取消任務,並返回一個boolean值。
相關文章
相關標籤/搜索