天天看點

Java多線程——中斷機制

前言:在Java多線程中,中斷一直圍繞着我們,當我們閱讀各種關于Java多線程的資料、書籍時,“中斷”一詞總是會出現,筆者對其的了解也是朦朦胧胧,是以非常有必要搞清楚Java多線程的中斷機制。

1.Java中斷機制是什麼

Java 中斷機制是一種協作機制,也就是說通過中斷并不能直接終止另一個線程,而需要被中斷的線程自己進行中斷。這好比老師要求學生要高品質完成作業,但是學生是否高品質完成作業,完全取決于學生自己。

Java 中斷模型非常的簡單:每個線程對象裡都有一個 boolean 類型的辨別(當然jdk源碼中是看不到該辨別位,它位于虛拟機層面),代表着是否有中斷請求(該請求可以來自所有線程,包括被中斷的線程本身)。例如,當線程 t1 想中斷線程 t2,隻需要線上程 t1 中将線程 t2 對象的中斷辨別置為 true,然後線程 t2 可以選擇在合适的時候處理該中斷請求,甚至可以不理會該請求,就像這個線程沒有被中斷一樣。

綜合上述兩段文字的描述,對Java中斷機制進行總結:Java中斷是一種機制,并不是真的停止線程,而是對線程對象打上一個中斷标記,具體如何處理還是要看被中斷線程如何操作。

2.Thread類提供的中斷相關方法

  • Java多線程——中斷機制

中斷線程,注意該方法未被static修飾,是以該方法被Thread對象調用。并且該方法僅僅是為線程打一個中斷的标記,将線程中斷狀态設定為true。

  • Java多線程——中斷機制

測試Thread對象是否中斷,主要該方法也未被static修飾,是以該方法也應該被Thread對象調用,如果線程被打了中斷标記,傳回true,否則傳回false。特别注意該方法不影響中斷狀态,這裡主要和interrupted()方法做對比。

  • Java多線程——中斷機制

測試目前線程是否中斷,注意該方法被static修飾,并且該方法會清除線程的中斷标記,将中斷标記設定為false。也就是說,如果連續兩次調用該方法,則第二次調用将傳回 false(在第一次調用已清除了其中斷狀态之後,且第二次調用檢驗完中斷狀态前,目前線程再次中斷的情況除外)。

總結:

#1.真正中斷線程的方法為interrupt(),并且該方法也僅僅是為Thread對象打一個中斷的标記,而不是立即終止線程。

#2.isInterrupted()方法未被static修飾,測試Thread對象是否中斷,也就是判斷線程對象是否有中斷标記。該方法不會清除中斷标記。

#3.interrupted()方法被static修飾,測試目前線程是否中斷,注意該方法會清除線程中斷的标記。

以上三個函數的源碼邏輯簡單,主要調用了native方法,這裡不進行闡述。

3.中斷處理時機

中斷作為一種協作機制,不會強求被中斷線程一定要在某個點進行處理。實際上,被中斷線程隻需在合适的時候處理即可,如果沒有合适的時間點,甚至可以不處理,這時候在任務處理層面,就跟沒有調用中斷方法一樣。“合适的時候”與線程正在處理的業務邏輯緊密相關。

處理時機決定着程式的效率與中斷響應的靈敏性,頻繁的檢查中斷狀态可能會使程式執行效率下降,相反,檢查的較少可能使中斷請求得不到及時響應。如果發出中斷請求之後,被中斷的線程繼續執行一段時間不會給系統帶來災難,那麼就可以将中斷處理放到友善檢查中斷,同時又能從一定程度上保證響應靈敏度的地方。當程式的性能名額比較關鍵時,可能需要建立一個測試模型來分析最佳的中斷檢測點,以平衡性能和響應靈敏性。

4.線程中斷舉例

  • 停不下來的線程
1 public static void main(String[] args) throws InterruptedException {
 2         Thread t1 = new Thread(() -> {
 3             for (int i = 0; i < 500000; i++) {
 4                 System.out.println("i=" + (i + 1));
 5             }
 6             System.out.println("我是t1線程");
 7         });
 8         t1.start();
 9         Thread.sleep(200);
10         t1.interrupt();
11     }      

上述代碼運作結果如下:

Java多線程——中斷機制

從運作結果來看,線程并未終止成功,這也符合interrupt()函數的功能描述,僅僅是為線程打一個中斷标記,具體怎麼處理還要看線程自己如何操作。

将上面代碼做如下修改:

1 public static void main(String[] args) throws InterruptedException {
 2         Thread t1 = new Thread(() -> {
 3             for (int i = 0; i < 500000; i++) {
 4                 if (Thread.currentThread().isInterrupted()) {  // 對中斷做處理
 5                     System.out.println("t1線程被中斷了");
 6                     return;
 7                 }
 8                 System.out.println("i=" + (i + 1));
 9             }
10             System.out.println("我是t1線程");
11         });
12         t1.start();
13         Thread.sleep(200);
14         t1.interrupt();
15     }      

其運作結果如下:

Java多線程——中斷機制

上述代碼對中斷進行了處理,是以循環并未走完,t1線程被成功中斷。

  • interrupted()和isInterrupted()的差別
1 public static void main(String[] args) throws InterruptedException {
 2         Thread t1 = new Thread(() -> {
 3             for (int i = 0; i < 500000; i++) {
 4                 System.out.println("i=" + (i + 1));
 5             }
 6             System.out.println("我是t1線程");
 7         });
 8         t1.start();
 9         Thread.sleep(200);
10         t1.interrupt();
11         System.out.println("isInterrupted()=" + t1.isInterrupted());
12         System.out.println("isInterrupted()=" + t1.isInterrupted());
13         System.out.println("interrupted()=" + t1.interrupted());
14         System.out.println("interrupted()=" + Thread.interrupted());
15     }      

運作結果如下:

Java多線程——中斷機制

為什麼會出現上面的運作結果呢,從源碼上最容易了解:

  • Java多線程——中斷機制
  • 該方法未被static修飾【isInterrupted(false)表示不會清除中斷标志,isInterrupted為native方法】,是以該方法被Thread對象調用,傳回Thread對象的中斷狀态。
  • Java多線程——中斷機制
  • 該方法被static修飾【注意isInterrupted(true)表示會清除中斷标志】,該方法傳回目前線程的中斷狀态,在上述代碼中,目前線程為main方法代表的主線程,并沒有進行中斷操作,是以列印結果為false。

修改上述代碼:

1 public static void main(String[] args) throws InterruptedException {
 2         Thread t1 = new Thread(() -> {
 3             for (int i = 0; i < 500000; i++) {
 4                 System.out.println("i=" + (i + 1));
 5             }
 6             System.out.println("我是t1線程");
 7         });
 8         t1.start();
 9         Thread.sleep(200);
10         t1.interrupt();
11         Thread.currentThread().interrupt();
12         System.out.println("isInterrupted()=" + t1.isInterrupted());
13         System.out.println("isInterrupted()=" + t1.isInterrupted());
14         System.out.println("interrupted()=" + t1.interrupted());
15         System.out.println("interrupted()=" + Thread.interrupted());
16     }      

注意第11行代碼,其運作結果如下:

Java多線程——中斷機制

從結果充分說明連續兩次調用interrupted()會清除中斷标記。

總結

通過上述的分析,對Java的中斷機制的核心要點做如下總結:

  • Java中斷機制是一種協作機制,中斷隻是給線程打一個中斷标記,具體如何操作還要看線程自己,by myself。
  • interrupt()函數作用僅僅是為線程打一個中斷标記。
  • interrupted()與isInterrupted()函數,都是傳回線程的中斷狀态,但是interrupted()被static修飾,傳回目前線程的中斷狀态,并且會清除線程的中斷标記;而isInterrupted()未被static修飾,被Thread對象調用,它不會清除線程的中斷标記。

by Shawn Chen,2019.02.17,上午。 

=========================================================

比你優秀的人比你還努力,你有什麼資格不去奮鬥!

__一個有理想的程式員。