Java提供了中斷機制,可使用它來結束一個線程。這種機制要求線程檢查它是否被中斷了,而後決定是否是響應這個中斷請求。線程容許忽略中斷請求並繼續執行。Java的中斷是一種協做機制。也就是說調用線程對象的interrupt方法並不必定就中斷了正在運行的線程,它只是要求線程本身在合適的時機中斷本身。Thread類有一個代表線程被中斷的屬性,它存放boolean值。線程的interrupted()方法被調用時,該值設爲true。isInterrupted()方法返回這個屬性的值。html
1、Java中斷的現象
首先,看看Thread類裏的幾個方法: java
public static boolean 程序員 interrupted()編程 |
測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,若是連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態以後,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的狀況除外)。 |
public boolean框架 isInterrupted()異步 |
測試線程是否已經中斷。線程的中斷狀態不受該方法的影響。 |
public void interrupt() | 中斷線程。 |
上面列出了與中斷有關的幾個方法及其行爲,能夠看到interrupt是中斷線程。若是不瞭解Java的中斷機制,這樣的一種解釋極容易形成誤解,認爲調用了線程的interrupt方法就必定會中斷線程。 Java的中斷是一種協做機制。也就是說調用線程對象的interrupt方法並不必定就中斷了正在運行的線程,它只是要求線程本身在合適的時機中斷本身。每一個線程都有一個boolean的中斷狀態(不必定就是對象的屬性,事實上,該狀態也確實不是Thread的字段),interrupt方法僅僅只是將該狀態置爲true 。代碼以下: socket
public class Test { public static void main(String[] args) { Thread t = new MyThread(); t.start(); t.interrupt(); System.out.println("已調用線程的interrupt方法"); } static class MyThread extends Thread { public void run() { int num = longTimeRunningNonInterruptMethod(2, 0); System.out.println("長時間任務運行結束,num=" + num); System.out.println("線程的中斷狀態:" + Thread.interrupted()); } private static int longTimeRunningNonInterruptMethod(int count,int initNum) { for (int i = 0; i < count; i++) { for (int j = 0; j < Integer.MAX_VALUE; j++) { initNum++; } } return initNum; } } }
通常狀況下,會打印以下內容:性能
已調用線程的interrupt方法 長時間任務運行結束,num=-2 線程的中斷狀態:true
可見,interrupt方法並不必定能中斷線程。可是,若是改爲下面的程序,狀況會怎樣呢? 代碼以下:測試
public class Test { public static void main(String[] args) { Thread t = new MyThread(); t.start(); t.interrupt(); System.out.println("已調用線程的interrupt方法"); } static class MyThread extends Thread { public void run() { int num = -1; try { num = longTimeRunningInterruptMethod(2, 0); } catch (InterruptedException e) { System.out.println("線程被中斷"); throw new RuntimeException(e); } System.out.println("長時間任務運行結束,num=" + num); System.out.println("線程的中斷狀態:" + Thread.interrupted()); } private static int longTimeRunningInterruptMethod(int count, int initNum) throws InterruptedException { for (int i = 0; i < count; i++) { TimeUnit.SECONDS.sleep(5); } return initNum; } } }
已調用線程的interrupt方法 線程被中斷 Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted at Chapter1.Test$MyThread.run(Test.java:20) Caused by: java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360) at Chapter1.Test$MyThread.longTimeRunningInterruptMethod(Test.java:29) at Chapter1.Test$MyThread.run(Test.java:17)
經運行能夠發現,程序拋出異常中止了,run方法裏的後兩條打印語句沒有執行。那麼,區別在哪裏?
一 般說來,若是一個方法聲明拋出InterruptedException,表示該方法是可中斷的(沒有在方法中處理中斷卻也聲明拋出 InterruptedException的除外),也就是說可中斷方法會對interrupt調用作出響應(例如sleep響應interrupt的操做包括清除中斷狀態,拋出InterruptedException),若是interrupt調用是在可中斷方法以前調用,可中斷方法必定會處理中斷,像上面的例子,interrupt方法很可能在run未進入sleep的時候就調用了,但sleep檢測到中斷,就會處理該中斷。若是在可中斷方法正在執行中的時候調用interrupt,會怎麼樣呢?這就要看可中斷方法處理中斷的時機了,只要可中斷方法能檢測到中斷狀態爲true,就應該處理中斷。讓咱們爲開頭的那段代碼加上中斷處理。 那麼自定義的可中斷方法該如何處理中斷呢?那就是在適合處理中斷的地方檢測線程中斷狀態並處理。代碼以下:spa
public class Test { public static void main(String[] args) throws Exception { Thread t = new MyThread(); t.start(); TimeUnit.MILLISECONDS.sleep(1);//若是不能看處處理過程當中被中斷的情形,能夠啓用這句再看看效果 t.interrupt(); System.out.println("已調用線程的interrupt方法"); } static class MyThread extends Thread { public void run() { int num; try { num = longTimeRunningNonInterruptMethod(2, 0); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("長時間任務運行結束,num=" + num); System.out.println("線程的中斷狀態:" + Thread.interrupted()); } private static int longTimeRunningNonInterruptMethod(int count,int initNum) throws InterruptedException { if (interrupted()) { throw new InterruptedException("正式處理前線程已經被請求中斷"); } for (int i = 0; i < count; i++) { for (int j = 0; j < Integer.MAX_VALUE; j++) { initNum++; } // 假如這就是一個合適的地方 if (interrupted()) { // 回滾數據,清理操做等 throw new InterruptedException("線程正在處理過程當中被中斷"); } } return initNum; } } }
如上面的代碼,方法longTimeRunningMethod此時已經是一個可中斷的方法了。在進入方法的時候判斷是否被請求中斷,若是是,就不進行相應的處理了;處理過程當中,可能也有合適的地方處理中斷,例如上面最內層循環結束後。這段代碼中檢測中斷用了Thread的靜態方法interrupted,它將中斷狀態置爲false,並將以前的狀態返回,而isInterrupted只是檢測中斷,並不改變中斷狀態。通常來講,處理過了中斷請求,應該將其狀態置爲false。但具體還要看實際情形。
2、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的實現和硬件的性能。
3、一些不會拋出 InterruptedException 的線程阻塞操做
然而,對於某些線程阻塞操做,JVM並不會自動拋出InterruptedException異常。例如,某些I/O操做和內部鎖操做。對於這類操做,能夠用其餘方式模擬中斷:
4、兩條編程原則
另外,在任務與線程分離的框架中,任務一般並不知道自身會被哪一個線程調用,也就不知道調用線程處理中斷的策略。因此,在任務設置了線程中斷標記後,並不能確保任務會被取消。所以,有如下兩條編程原則:
總之,Java中的非搶佔式中斷機制,要求咱們必須改變傳統的搶佔式中斷思路,在理解其本質的基礎上,採用相應的原則和模式來編程