Java中斷機制是一種協做機制,也就是說經過中斷並不能直接終止另外一個線程,它只是要求被中斷線程在合適的時機中斷本身,這須要被中斷的線程本身處理中斷。這比如是家裏的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎麼注意身體則徹底取決於本身。html
通常說來,當可能阻塞的方法聲明中有拋出InterruptedException則暗示該方法是可中斷的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep、condition.await以及可中斷的通道上的 I/O 操做方法等,若是程序捕獲到這些可中斷的阻塞方法拋出的InterruptedException或檢測到中斷後,這些中斷信息該如何處理?通常有如下兩個通用原則:
若是遇到的是可中斷的阻塞方法拋出InterruptedException,能夠繼續向方法調用棧的上層拋出該異常,若是是檢測到中斷,則可清除中斷狀態並拋出InterruptedException,使當前方法也成爲一個可中斷的方法。
如有時候不太方便在方法上拋出InterruptedException,好比要實現的某個接口中的方法簽名上沒有throws InterruptedException,這時就能夠捕獲可中斷方法的InterruptedException並經過Thread.currentThread.interrupt()來從新設置中斷狀態。若是是檢測並清除了中斷狀態,亦是如此。
通常的代碼中,尤爲是做爲一個基礎類庫時,毫不應當吞掉中斷,即捕獲到InterruptedException後在catch裏什麼也不作,清除中斷狀態後又不重設中斷狀態也不拋出InterruptedException等。由於吞掉中斷狀態會致使方法調用棧的上層得不到這些信息。
固然,凡事總有例外的時候,當你徹底清楚本身的方法會被誰調用,而調用者也不會由於中斷被吞掉了而遇到麻煩,就能夠這麼作。
總得來講,就是要讓方法調用棧的上層獲知中斷的發生。假設你寫了一個類庫,類庫裏有個方法amethod,在amethod中檢測並清除了中斷狀態,而沒有拋出InterruptedException,做爲amethod的用戶來講,他並不知道里面的細節,若是用戶在調用amethod後也要使用中斷來作些事情,那麼在調用amethod以後他將永遠也檢測不到中斷了,由於中斷信息已經被amethod清除掉了。若是做爲用戶,遇到這樣有問題的類庫,又不能修改代碼,那該怎麼處理?只好在本身的類裏設置一個本身的中斷狀態,在調用interrupt方法的時候,同時設置該狀態,這實在是無路可走時才使用的方法。
注意:synchronized在獲鎖的過程當中是不能被中斷的,意思是說若是產生了死鎖,則不可能被中斷(請參考後面的測試例子)。與synchronized功能類似的reentrantLock.lock()方法也是同樣,它也不可中斷的,即若是發生死鎖,那麼reentrantLock.lock()方法沒法終止,若是調用時被阻塞,則它一直阻塞到它獲取到鎖爲止。可是若是調用帶超時的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那麼若是線程在等待時被中斷,將拋出一個InterruptedException異常,這是一個很是有用的特性,由於它容許程序打破死鎖。你也能夠調用reentrantLock.lockInterruptibly()方法,它就至關於一個超時設爲無限的tryLock方法。java
interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已。異步
即上面狀態圖中的:「等待Blocked」-->「鎖定Blocked」ide
一、interrupt()
interrupt方法用於中斷線程。調用該方法的線程的狀態爲將被置爲"中斷"狀態。
注意:線程中斷僅僅是置線程的中斷狀態位,不會中止線程。須要用戶本身去監視線程的狀態爲並作處理。支持線程中斷的方法(也就是線程中斷後會拋出interruptedException的方法)就是在監視線程的中斷狀態,一旦線程的中斷狀態被置爲「中斷狀態」,就會拋出中斷異常。
性能
二、interrupted()測試
public static boolean interrupted() { return currentThread().isInterrupted(true); }
該方法就是直接調用當前線程的isInterrupted(true)的方法。靜態方法interrupted會將當前線程的中斷狀態清除,但這個方法的命名極不直觀,很容易形成誤解,須要特別注意。
三、isInterrupted()this
public boolean isInterrupted() { return isInterrupted(false); } private native boolean isInterrupted(boolean flag);
該方法卻直接調用當前線程的native isInterrupted(false)的方法,不清除中斷狀態。spa
所以interrupted()和isInterrupted()這兩個方法主要區別:這兩個方法最終都會調用同一個方法-----isInterrupted( Boolean 參數)(重載方法,是私有的native方法),只不過參數固定爲一個是true,一個是false;線程
因爲第二個區別主要體如今調用的方法的參數上,讓咱們來看一看這個參數是什麼含義:
先來看一看被調用的方法 isInterrupted(boolean arg)(Thread類中重載的方法)的定義:code
private native boolean isInterrupted( boolean flag);
這個本地方法,看不到源碼。做用是要清除狀態位。若是這個參數爲true,說明返回線程的狀態位後,要清掉原來的狀態位(恢復成原來狀況)。這個參數爲false,就是直接返回線程的狀態位。
這兩個方法很好區分,只有當前線程才能清除本身的中斷位(對應interrupted()方法)
interrupt()只是改變中斷狀態而已,interrupt()不會中斷(不是中止線程)一個正在運行的線程。這一方法實際上完成的是,給線程拋出一箇中斷信號, 這樣受阻線程就得以退出阻塞的狀態(不會中止線程。須要用戶本身去監視線程的狀態爲並作處理)。更確切的說,若是線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提前地終結被阻塞狀態。
或者能夠簡單總結一下中斷兩種狀況:
例如:
線程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是不會被拋出來的。
注意1:當線程A執行到wait(),sleep(),join()時,拋出InterruptedException後,中斷狀態已經被系統復位了,線程A調用Thread.interrupted()返回的是false。
示例:interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已。
package com.dxz; public class InterruptionInJava implements Runnable { public static void main(String[] args) throws InterruptedException { Thread testThread = new Thread(new InterruptionInJava(), "InterruptionInJava"); // start thread testThread.start(); Thread.sleep(1000); // interrupt thread testThread.interrupt(); System.out.println("main end"); } @Override public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("Yes,I am interruted,but I am still running"); } else { System.out.println("not yet interrupted"); } } } }
結果:
interrupt()不能中斷在運行中的線程,它只能改變中斷狀態而已,那麼,如何正確中斷?既然是隻能修改中斷狀態,那麼咱們應該針對中斷狀態作些什麼?
修改代碼,在狀態判斷中如上,添加一個return就ok了。但現實中,咱們可能須要作的更通用,不由又要發出天問,如何中斷線程?答案是添加一個開關。
//... if(Thread.currentThread().isInterrupted()){ System.out.println("Yes,I am interruted,but I am still running"); return; //...
線程A正在使用sleep()暫停着: Thread.sleep(100000);
若是要取消他的等待狀態,能夠在正在執行的線程裏(好比這裏是B)調用。
a.interrupt();
令線程A放棄睡眠操做,這裏a是線程A對應到的Thread實例
當在sleep中時 線程被調用interrupt()時,就立刻會放棄暫停的狀態.並拋出InterruptedException.丟出異常的,是A線程。
package com.dxz.interrupt; public class ThreadDemo1A extends Thread { @Override public void run() { try { System.out.println("線程a開始工做"); sleep(30000); } catch (InterruptedException e) { System.out.println(this.isInterrupted()); } } } package com.dxz.interrupt; import java.util.concurrent.TimeUnit; public class ThreadDemo1B { public static void main(String[] args) throws InterruptedException { ThreadDemo1A threada = new ThreadDemo1A(); threada.start(); TimeUnit.SECONDS.sleep(3); System.out.println("開始中斷線程a"); threada.interrupt(); } }
結果:
線程a開始工做 開始中斷線程a false
線程A調用了wait()進入了等待狀態,也能夠用interrupt()取消。
不過這時候要當心鎖定的問題.線程在進入等待區,會把鎖定解除,當對等待中的線程調用interrupt()時,會先從新獲取鎖定,再拋出異常。在獲取鎖定以前,是沒法拋出異常的。
package com.dxz.interrupt; public class ThreadDemo2A extends Thread { @Override public void run() { try { System.out.println("線程a開始工做"); synchronized (this) { wait(30000); } } catch (InterruptedException e) { System.out.println(this.isInterrupted()); } } } package com.dxz.interrupt; import java.util.concurrent.TimeUnit; public class ThreadDemo2B { public static void main(String[] args) throws InterruptedException { ThreadDemo2A threada = new ThreadDemo2A(); threada.start(); TimeUnit.SECONDS.sleep(3); System.out.println("開始中斷線程a"); threada.interrupt(); } }
結果:
線程a開始工做 開始中斷線程a false
當線程以join()等待其餘線程結束時,當它被調用interrupt(),它與sleep()時同樣, 會立刻跳到catch塊裏。注意,是對誰調用interrupt()方法,必定是調用被阻塞線程的interrupt方法.如在線程a中調用來線程t.join(),
則a會等t執行完後在執行t.join後的代碼,當在線程b中調用a.interrupt()方法,則會拋出InterruptedException,固然線程t的join()也就被取消了。線程t還會繼續運行下去。
package com.dxz.interrupt; public class T extends Thread { public T(String s) { super(s); } @Override public void run() { System.out.println("線程t開始進入死循環"); for(;;); } } package com.dxz.interrupt; public class ThreadDemo3A extends Thread { public ThreadDemo3A(String s) { super(s); } @Override public void run() { try { System.out.println("線程a等待線程t執行"); T t = new T("demo3t"); t.start(); t.join(); System.out.println("線程a中的線程t已經結束"); } catch (InterruptedException e) { System.out.println(this.isInterrupted()); } } } package com.dxz.interrupt; import java.util.concurrent.TimeUnit; public class ThreadDemo3B { public static void main(String[] args) { ThreadDemo3A threada = new ThreadDemo3A("demo3a"); threada.start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("開始中斷線程a"); threada.interrupt(); System.out.println("--------"); } }
結果:
線程a等待線程t執行 線程t開始進入死循環 開始中斷線程a -------- false
能夠看到T線程在主線程結束了,還在運行。
package com.dxz.synchronize; import java.util.concurrent.TimeUnit; public class InterruputThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread() { @Override public void run() { while (true) { // 判斷當前線程是否被中斷 if (this.isInterrupted()) { System.out.println("線程中斷"); break; } } System.out.println("已跳出循環,線程中斷!"); } }; t1.start(); TimeUnit.SECONDS.sleep(2); t1.interrupt(); } }
結果:
線程中斷
已跳出循環,線程中斷!
此外,類庫中的有些類的方法也可能會調用中斷,如FutureTask中的cancel方法,若是傳入的參數爲true,它將會在正在運行異步任務的線程上調用interrupt方法,若是正在執行的異步任務中的代碼沒有對中斷作出響應,那麼cancel方法中的參數將不會起到什麼效果;又如ThreadPoolExecutor中的shutdownNow方法會遍歷線程池中的工做線程並調用線程的interrupt方法來中斷線程,因此若是工做線程中正在執行的任務沒有對中斷作出響應,任務將一直執行直到正常結束。
判斷某個線程是否已被髮送過中斷請求,請使用Thread.currentThread().isInterrupted()方法(由於它將線程中斷標示位設置爲true後,不會馬上清除中斷標示位,即不會將中斷標設置爲false),而不要使用thread.interrupted()(該方法調用後會將中斷標示位清除,即從新設置爲false)方法來判斷,下面是線程在循環中時的中斷方式:
while(!Thread.currentThread().isInterrupted() && more work to do){
do more work
}
顯然,做爲一種協做機制,不會強求被中斷線程必定要在某個點進行處理。實際上,被中斷線程只需在合適的時候處理便可,若是沒有合適的時間點,甚至能夠不處理,這時候在任務處理層面,就跟沒有調用中斷方法同樣。「合適的時候」與線程正在處理的業務邏輯緊密相關,例如,每次迭代的時候,進入一個可能阻塞且沒法中斷的方法以前等,但多半不會出如今某個臨界區更新另外一個對象狀態的時候,由於這可能會致使對象處於不一致狀態。
處理時機決定着程序的效率與中斷響應的靈敏性。頻繁的檢查中斷狀態可能會使程序執行效率降低,相反,檢查的較少可能使中斷請求得不到及時響應。若是發出中斷請求以後,被中斷的線程繼續執行一段時間不會給系統帶來災難,那麼就能夠將中斷處理放到方便檢查中斷,同時又能從必定程度上保證響應靈敏度的地方。當程序的性能指標比較關鍵時,可能須要創建一個測試模型來分析最佳的中斷檢測點,以平衡性能和響應靈敏性。
處理方式
程序裏發現中斷後該怎麼響應?這就得視實際狀況而定了。有些程序可能一檢測到中斷就立馬將線程終止,有些多是退出當前執行的任務,繼續執行下一個任務……做爲一種協做機制,這要與中斷方協商好,當調用interrupt會發生些什麼都是事先知道的,如作一些事務回滾操做,一些清理工做,一些補償操做等。若不肯定調用某個線程的interrupt後該線程會作出什麼樣的響應,那就不該當中斷該線程。
Thread.stop方法已經不推薦使用了。而在某些方面Thread.stop與中斷機制有着類似之處。如當線程在等待內置鎖或IO時,stop跟interrupt同樣,不會停止這些操做;當catch住stop致使的異常時,程序也能夠繼續執行,雖然stop本意是要中止線程,這麼作會讓程序行爲變得更加混亂。
那麼它們的區別在哪裏?最重要的就是中斷須要程序本身去檢測而後作相應的處理,而Thread.stop會直接在代碼執行過程當中拋出ThreadDeath錯誤,這是一個java.lang.Error的子類。