java面試-Java併發程式設計(五)——中斷

追風箏的豬發表於2018-03-19

什麼是中斷?

在Java中沒有辦法立即停止一條執行緒,然而停止執行緒卻顯得尤為重要,如取消一個耗時操作。因此,Java提供了一種用於停止執行緒的機制——中斷。

  • 中斷只是一種協作機制,Java沒有給中斷增加任何語法,中斷的過程完全需要程式設計師自己實現。若要中斷一個執行緒,你需要手動呼叫該執行緒的interrupted方法,該方法也僅僅是將執行緒物件的中斷標識設成true;接著你需要自己寫程式碼不斷地檢測當前執行緒的標識位;如果為true,表示別的執行緒要求這條執行緒中斷,此時究竟該做什麼需要你自己寫程式碼實現。
  • 每個執行緒物件中都有一個標識,用於表示執行緒是否被中斷;該標識位為true表示中斷,為false表示未中斷;
  • 通過呼叫執行緒物件的interrupt方法將該執行緒的標識位設為true;可以在別的執行緒中呼叫,也可以在自己的執行緒中呼叫。

中斷的相關方法

  • public void interrupt() 
    將呼叫者執行緒的中斷狀態設為true。

  • public boolean isInterrupted() 
    判斷呼叫者執行緒的中斷狀態。

  • public static boolean interrupted 
    只能通過Thread.interrupted()呼叫。 
    它會做兩步操作:

    1. 返回當前執行緒的中斷狀態;
    2. 將當前執行緒的中斷狀態設為false;

暫停、繼續、停止執行緒(已過時)

以下三個方法都是通過執行緒物件去呼叫。

  • suspend() 
    暫停呼叫者執行緒,只釋放CPU執行權,不釋放鎖。 
    由於在不釋放資源的情況下進入睡眠狀態,容易產生死鎖。因此已過時!

  • resume() 
    恢復呼叫者執行緒,讓他處於就緒狀態。

  • stop() 
    呼叫stop後,並不會保證資源被正確地釋放,它會使程式處於不正確的狀態下。

PS:stop和interrupt的區別?

中斷的使用

要使用中斷,首先需要在可能會發生中斷的執行緒中不斷監聽中斷狀態,一旦發生中斷,就執行相應的中斷處理程式碼。 
當需要中斷執行緒時,呼叫該執行緒物件的interrupt函式即可。

設定中斷監聽

Thread t1 = new Thread( new Runnable(){
    public void run(){
        // 若未發生中斷,就正常執行任務
        while(!Thread.currentThread.isInterrupted()){
            // 正常任務程式碼……
        }

        // 中斷的處理程式碼……
        doSomething();
    }
} ).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

正常的任務程式碼被封裝在while迴圈中,每次執行完一遍任務程式碼就檢查一下中斷狀態;一旦發生中斷,則跳過while迴圈,直接執行後面的中斷處理程式碼。

觸發中斷

t1.interrupt();
  • 1

上述程式碼執行後會將t1物件的中斷狀態設為true,此時t1執行緒的正常任務程式碼執行完成後,進入下一次while迴圈前Thread.currentThread.isInterrupted()的結果為true,此時退出迴圈,執行迴圈後面的中斷處理程式碼。

安全地停止執行緒

stop函式停止執行緒過於暴力,它會立即停止執行緒,不給任何資源釋放的餘地,下面介紹兩種安全停止執行緒的方法。

迴圈標記變數

自定義一個共享的boolean型別變數,表示當前執行緒是否需要中斷。

  • 中斷標識
volatile boolean interrupted = false;
  • 1
  • 任務執行函式
Thread t1 = new Thread( new Runnable(){
    public void run(){
        while(!interrupted){
            // 正常任務程式碼……
        }
        // 中斷處理程式碼……
        // 可以在這裡進行資源的釋放等操作……
    }
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 中斷函式
Thread t2 = new Thread( new Runnable(){
    public void run(){
        interrupted = true;
    }
} );
  • 1
  • 2
  • 3
  • 4
  • 5

迴圈中斷狀態

  • 中斷標識 
    由執行緒物件提供,無需自己定義。

  • 任務執行函式

Thread t1 = new Thread( new Runnable(){
    public void run(){
        while(!Thread.currentThread.isInterrupted()){
            // 正常任務程式碼……
        }
        // 中斷處理程式碼……
        // 可以在這裡進行資源的釋放等操作……
    }
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 中斷函式
t1.interrupt();
  • 1

總結

上述兩種方法本質一樣,都是通過迴圈檢視一個共享標記為來判斷執行緒是否需要中斷,他們的區別在於:第一種方法的標識位是我們自己設定的,而第二種方法的標識位是Java提供的。除此之外,他們的實現方法是一樣的。

上述兩種方法之所以較為安全,是因為一條執行緒發出終止訊號後,接收執行緒並不會立即停止,而是將本次迴圈的任務執行完,再跳出迴圈停止執行緒。此外,程式設計師又可以在跳出迴圈後新增額外的程式碼進行收尾工作。

處理中斷

上文都在介紹如何獲取中斷狀態,那麼當我們捕獲到中斷狀態後,究竟如何處理呢?

  • Java類庫中提供的一些可能會發生阻塞的方法都會拋InterruptedException異常,如:BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep。
  • 當你在某一條執行緒中呼叫這些方法時,這個方法可能會被阻塞很長時間,你可以在別的執行緒中呼叫當前執行緒物件的interrupt方法觸發這些函式丟擲InterruptedException異常。
  • 當一個函式丟擲InterruptedException異常時,表示這個方法阻塞的時間太久了,別人不想等它執行結束了。
  • 當你的捕獲到一個InterruptedException異常後,亦可以處理它,或者向上丟擲。
  • 丟擲時要注意???:當你捕獲到InterruptedException異常後,當前執行緒的中斷狀態已經被修改為false(表示執行緒未被中斷);此時你若能夠處理中斷,則不用理會該值;但如果你繼續向上拋InterruptedException異常,你需要再次呼叫interrupt方法,將當前執行緒的中斷狀態設為true。
  • 注意:絕對不能“吞掉中斷”!即捕獲了InterruptedException而不作任何處理。這樣違背了中斷機制的規則,別人想讓你執行緒中斷,然而你自己不處理,也不將中斷請求告訴呼叫者,呼叫者一直以為沒有中斷請求。

QA

  1. 為什麼catch InterruptedException後會自動清除中斷狀態?


相關文章