(一).關於interrupt()
interrupt()並不直接中斷執行緒,而是設定一箇中斷標識,然後由程式進行中斷檢查,確定是否中斷。
1. sleep() & interrupt()
執行緒A正在使用sleep()暫停著: Thread.sleep(100000);
如果要取消他的等待狀態,可以在正在執行的執行緒裡(比如這裡是B)呼叫a.interrupt();
令執行緒A放棄睡眠操作,這裡a是執行緒A對應到的Thread例項執行interrupt()時,並不需要獲取Thread例項的鎖定.任何執行緒在任何時刻,都可以呼叫其他執行緒interrupt().當sleep中的執行緒被呼叫interrupt()時,就會放棄暫停的狀態.並丟擲InterruptedException.丟出異常的,是A執行緒.
執行緒A正在使用sleep()暫停著: Thread.sleep(100000);
如果要取消他的等待狀態,可以在正在執行的執行緒裡(比如這裡是B)呼叫a.interrupt();
令執行緒A放棄睡眠操作,這裡a是執行緒A對應到的Thread例項執行interrupt()時,並不需要獲取Thread例項的鎖定.任何執行緒在任何時刻,都可以呼叫其他執行緒interrupt().當sleep中的執行緒被呼叫interrupt()時,就會放棄暫停的狀態.並丟擲InterruptedException.丟出異常的,是A執行緒.
2. wait() & interrupt()
執行緒A呼叫了wait()進入了等待狀態,也可以用interrupt()取消.
不過這時候要小心鎖定的問題.執行緒在進入等待區,會把鎖定解除,當對等待中的執行緒呼叫interrupt()時(注意是等待的執行緒呼叫其自己的interrupt()),會先重新獲取鎖定,再丟擲異常.在獲取鎖定之前,是無法丟擲異常的.
執行緒A呼叫了wait()進入了等待狀態,也可以用interrupt()取消.
不過這時候要小心鎖定的問題.執行緒在進入等待區,會把鎖定解除,當對等待中的執行緒呼叫interrupt()時(注意是等待的執行緒呼叫其自己的interrupt()),會先重新獲取鎖定,再丟擲異常.在獲取鎖定之前,是無法丟擲異常的.
3. join() & interrupt()
當執行緒以join()等待其他執行緒結束時,一樣可以使用interrupt()取消之.因為呼叫join()不需要獲取鎖定,故與sleep()時一樣,會馬上跳到catch塊裡. 注意是隨呼叫interrupt()方法,一定是阻塞的執行緒來呼叫其自己的interrupt方法.如線上程a中呼叫來執行緒t.join().則a會等t執行完後在執行t.join後的程式碼,當線上程b中呼叫來a.interrupt()方法,則會丟擲InterruptedException
當執行緒以join()等待其他執行緒結束時,一樣可以使用interrupt()取消之.因為呼叫join()不需要獲取鎖定,故與sleep()時一樣,會馬上跳到catch塊裡. 注意是隨呼叫interrupt()方法,一定是阻塞的執行緒來呼叫其自己的interrupt方法.如線上程a中呼叫來執行緒t.join().則a會等t執行完後在執行t.join後的程式碼,當線上程b中呼叫來a.interrupt()方法,則會丟擲InterruptedException
4. interrupt()只是改變中斷狀態而已
interrupt()不會中斷一個正在執行的執行緒。這一方法實際上完成的是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。更確切的說,如果執行緒被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
如果執行緒沒有被阻塞,這時呼叫interrupt()將不起作用;否則,執行緒就將得到異常(該執行緒必須事先預備好處理此狀況),接著逃離阻塞狀態。
執行緒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是不會被丟擲來的.
interrupt()不會中斷一個正在執行的執行緒。這一方法實際上完成的是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。更確切的說,如果執行緒被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
如果執行緒沒有被阻塞,這時呼叫interrupt()將不起作用;否則,執行緒就將得到異常(該執行緒必須事先預備好處理此狀況),接著逃離阻塞狀態。
執行緒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是不會被丟擲來的.
(二)Java執行緒中斷的本質和程式設計原則
在歷史上,Java試圖提供過搶佔式限制中斷,但問題多多,例如前文介紹的已被廢棄的Thread.stop、Thread.suspend和 Thread.resume等。另一方面,出於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);
}
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() { ... }
...
}
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
你可能想,如果JVM只提供了這種簡陋的中斷機制,那和應用程式自己定義中斷變數並輪詢的方法相比,基本也沒有什麼優勢。
JVM內部中斷變數的主要優勢,就是對於某些情況,提供了模擬自動“中斷陷入”的機制。
在執行涉及執行緒排程的阻塞呼叫時(例如wait、sleep和join),如果發生中斷,被阻塞執行緒會“儘可能快的”丟擲InterruptedException。因此,我們就可以用下面的程式碼框架來處理執行緒阻塞中斷:
try {
//wait、sleep或join
}
catch(InterruptedException e) {
//某些中斷處理工作
catch(InterruptedException e) {
//某些中斷處理工作
}
所謂“儘可能快”,我猜測JVM就是線上程排程排程的間隙檢查中斷變數,速度取決於JVM的實現和硬體的效能。
所謂“儘可能快”,我猜測JVM就是線上程排程排程的間隙檢查中斷變數,速度取決於JVM的實現和硬體的效能。
然而,對於某些執行緒阻塞操作,JVM並不會自動丟擲InterruptedException異常。例如,某些I/O操作和內部鎖操作。對於這類操作,可以用其他方式模擬中斷:
1)java.io中的非同步socket I/O
讀寫socket的時候,InputStream和OutputStream的read和write方法會阻塞等待,但不會響應java中斷。不過,呼叫Socket的close方法後,被阻塞執行緒會丟擲SocketException異常。
2)利用Selector實現的非同步I/O
如果執行緒被阻塞於Selector.select(在java.nio.channels中),呼叫wakeup方法會引起ClosedSelectorException異常。
3)鎖獲取
如果執行緒在等待獲取一個內部鎖,我們將無法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時,提供中斷能力。
另外,在任務與執行緒分離的框架中,任務通常並不知道自身會被哪個執行緒呼叫,也就不知道呼叫執行緒處理中斷的策略。所以,在任務設定了執行緒中斷標記後,並不能確保任務會被取消。因此,有以下兩條程式設計原則:
1)除非你知道執行緒的中斷策略,否則不應該中斷它。
這條原則告訴我們,不應該直接呼叫Executer之類框架中執行緒的interrupt方法,應該利用諸如Future.cancel的方法來取消任務。
2)任務程式碼不該猜測中斷對執行執行緒的含義。
這條原則告訴我們,一般程式碼遇在到InterruptedException異常時,不應該將其捕獲後“吞掉”,而應該繼續向上層程式碼丟擲。
總之,Java中的非搶佔式中斷機制,要求我們必須改變傳統的搶佔式中斷思路,在理解其本質的基礎上,採用相應的原則和模式來程式設計。
1)java.io中的非同步socket I/O
讀寫socket的時候,InputStream和OutputStream的read和write方法會阻塞等待,但不會響應java中斷。不過,呼叫Socket的close方法後,被阻塞執行緒會丟擲SocketException異常。
2)利用Selector實現的非同步I/O
如果執行緒被阻塞於Selector.select(在java.nio.channels中),呼叫wakeup方法會引起ClosedSelectorException異常。
3)鎖獲取
如果執行緒在等待獲取一個內部鎖,我們將無法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時,提供中斷能力。
另外,在任務與執行緒分離的框架中,任務通常並不知道自身會被哪個執行緒呼叫,也就不知道呼叫執行緒處理中斷的策略。所以,在任務設定了執行緒中斷標記後,並不能確保任務會被取消。因此,有以下兩條程式設計原則:
1)除非你知道執行緒的中斷策略,否則不應該中斷它。
這條原則告訴我們,不應該直接呼叫Executer之類框架中執行緒的interrupt方法,應該利用諸如Future.cancel的方法來取消任務。
2)任務程式碼不該猜測中斷對執行執行緒的含義。
這條原則告訴我們,一般程式碼遇在到InterruptedException異常時,不應該將其捕獲後“吞掉”,而應該繼續向上層程式碼丟擲。
總之,Java中的非搶佔式中斷機制,要求我們必須改變傳統的搶佔式中斷思路,在理解其本質的基礎上,採用相應的原則和模式來程式設計。
(三) interrupt() 與 cancel()的區別
兩者實際上都是中斷執行緒,但是後者更安全、有條理和高效,其原因跟推薦使用Executor而不直接使用Thread類是一致的。所以結合上面講到的原則,我們應儘量採用cancel()方法,呼叫執行緒管理器ExecutorService介面的submit(Runnable task) 方法會返回一個Future<?>物件,然後呼叫Future.cancel()的方法來取消任務,並返回一個boolean值。
【Future】
http://www.gznc.edu.cn/yxsz/jjglxy/book/Java_api/java/util/concurrent/Future.html
【好奇】
(1)future.cancel(mayInterruptIfRunning)的內部實現會是什麼樣子的?可以中斷一個執行緒池裡正在執行著的“那一個”任務。
可猜想,必定記錄著具體執行緒標識,且發了一箇中斷訊號。
(2)猜測,應該只是發一箇中斷訊號,可以中斷阻塞中的操作。而如果是while(true); 這樣的佔用CPU的非阻塞式操作,是中斷不掉的,也即執行緒依舊在跑,佔用著執行緒池資源。
【注意】
a). 執行緒池資源有限,有些任務會submit()不進去,拋異常:java.util.concurrent.RejectedExecutionException
b).只要submit()成功的,無論是執行緒正在執行,或是在BlockingQueue中等待執行,future.cancel()操作均可中斷掉執行緒。也即,與其真正執行並無關係,阻塞中或等待被排程執行中,都將被中斷。
【demo示例】
future.cancel中斷阻塞操作:
public class Main {
/** 訊號量 */
private Semaphore semaphore = new Semaphore(0); // 1
/** 執行緒池 */
private ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
/** Future */
private Future<String> future ;
public void test(){
future = pool.submit(new Callable<String>() {
@Override
public String call() {
String result = null;
try {
// 同步阻塞獲取訊號量
semaphore.acquire();
result = "ok";
} catch (InterruptedException e) {
result = "interrupted";
}
return result;
}
});
String result = "timeout";
try {
// 等待3s
result = future.get(3, TimeUnit.SECONDS);
}catch (Exception e) {
System.out.println("超時異常");
}
// 刪除執行緒池中任務
boolean cancelResult = future.cancel(true);
System.out.println("result is " + result);
System.out.println("刪除結果:" +cancelResult);
System.out.println("當前active執行緒數:" +pool.getActiveCount());
}
public static void main(String[] args) {
Main o = new Main();
o.test();
}
}
http://www.cnblogs.com/alipayhutu/archive/2012/06/20/2556091.html
http://blog.sina.com.cn/s/blog_00ccd2400100nq7o.html