前言:在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 }
上述代碼運作結果如下:
從運作結果來看,線程并未終止成功,這也符合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 }
其運作結果如下:
上述代碼對中斷進行了處理,是以循環并未走完,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多線程——中斷機制 - 該方法未被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行代碼,其運作結果如下:
從結果充分說明連續兩次調用interrupted()會清除中斷标記。
總結
通過上述的分析,對Java的中斷機制的核心要點做如下總結:
- Java中斷機制是一種協作機制,中斷隻是給線程打一個中斷标記,具體如何操作還要看線程自己,by myself。
- interrupt()函數作用僅僅是為線程打一個中斷标記。
- interrupted()與isInterrupted()函數,都是傳回線程的中斷狀态,但是interrupted()被static修飾,傳回目前線程的中斷狀态,并且會清除線程的中斷标記;而isInterrupted()未被static修飾,被Thread對象調用,它不會清除線程的中斷标記。
by Shawn Chen,2019.02.17,上午。