1. 程式人生 > >Java併發程式設計(2):執行緒中斷(含程式碼)

Java併發程式設計(2):執行緒中斷(含程式碼)

使用interrupt()中斷執行緒
當一個執行緒執行時,另一個執行緒可以呼叫對應的Thread物件的interrupt()方法來中斷它,該方法只是在目標執行緒中設定一個標誌,表示它已經被中斷,並立即返回。這裡需要注意的是,如果只是單純的呼叫interrupt()方法,執行緒並沒有實際被中斷,會繼續往下執行。

下面一段程式碼演示了休眠執行緒的中斷:

public class SleepInterrupt extends Object implements Runnable{
public void run(){
try{
System.out.println("in run() - about to sleep for 20 seconds");
Thread.sleep(20000);
System.out.println("in run() - woke up");
}catch(InterruptedException e){
System.out.println("in run() - interrupted while sleeping");
//處理完中斷異常後,返回到run()方法人口,
//如果沒有return,執行緒不會實際被中斷,它會繼續列印下面的資訊
return;
}
System.out.println("in run() - leaving normally");
}

public static void main(String[] args) {
SleepInterrupt si = new SleepInterrupt();
Thread t = new Thread(si);
t.start();
//主執行緒休眠2秒,從而確保剛才啟動的執行緒有機會執行一段時間
try {
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("in main() - interrupting other thread");
//中斷執行緒t
t.interrupt();
System.out.println("in main() - leaving");
}
}
執行結果如下:

主執行緒啟動新執行緒後,自身休眠2秒鐘,允許新執行緒獲得執行時間。新執行緒列印資訊“about to sleep for 20 seconds”後,繼而休眠20秒鐘,大約2秒鐘後,main執行緒通知新執行緒中斷,那麼新執行緒的20秒的休眠將被打斷,從而丟擲InterruptException異常,執行跳轉到catch塊,打印出“interrupted while sleeping”資訊,並立即從run()方法返回,然後消亡,而不會打印出catch塊後面的“leaving normally”資訊。

請注意:由於不確定的執行緒規劃,上圖執行結果的後兩行可能順序相反,這取決於主執行緒和新執行緒哪個先消亡。但前兩行資訊的順序必定如上圖所示。

另外,如果將catch塊中的return語句註釋掉,則執行緒在丟擲異常後,會繼續往下執行,而不會被中斷,從而會打印出”leaving normally“資訊。

待決中斷
在上面的例子中,sleep()方法的實現檢查到休眠執行緒被中斷,它會相當友好地終止執行緒,並丟擲InterruptedException異常。另外一種情況,如果執行緒在呼叫sleep()方法前被中斷,那麼該中斷稱為待決中斷,它會在剛呼叫sleep()方法時,立即丟擲InterruptedException異常。

下面的程式碼演示了待決中斷:

public class PendingInterrupt extends Object {
public static void main(String[] args){
//如果輸入了引數,則在mian執行緒中中斷當前執行緒(亦即main執行緒)
if( args.length > 0 ){
Thread.currentThread().interrupt();
}
//獲取當前時間
long startTime = System.currentTimeMillis();
try{
Thread.sleep(2000);
System.out.println("was NOT interrupted");
}catch(InterruptedException x){
System.out.println("was interrupted");
}
//計算中間程式碼執行的時間
System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime));
}
}
如果PendingInterrupt不帶任何命令列引數,那麼執行緒不會被中斷,最終輸出的時間差距應該在2000附近(具體時間由系統決定,不精確),如果PendingInterrupt帶有命令列引數,則呼叫中斷當前執行緒的程式碼,但main執行緒仍然執行,最終輸出的時間差距應該遠小於2000,因為執行緒尚未休眠,便被中斷,因此,一旦呼叫sleep()方法,會立即打印出catch塊中的資訊。執行結果如下:

這種模式下,main執行緒中斷它自身。除了將中斷標誌(它是Thread的內部標誌)設定為true外,沒有其他任何影響。執行緒被中斷了,但main執行緒仍然執行,main執行緒繼續監視實時時鐘,並進入try塊,一旦呼叫sleep()方法,它就會注意到待決中斷的存在,並丟擲InterruptException。於是執行跳轉到catch塊,並打印出執行緒被中斷的資訊。最後,計算並打印出時間差。

使用isInterrupted()方法判斷中斷狀態
可以在Thread物件上呼叫isInterrupted()方法來檢查任何執行緒的中斷狀態。這裡需要注意:執行緒一旦被中斷,isInterrupted()方法便會返回true,而一旦sleep()方法丟擲異常,它將清空中斷標誌,此時isInterrupted()方法將返回false。

下面的程式碼演示了isInterrupted()方法的使用:

public class InterruptCheck extends Object{
public static void main(String[] args){
Thread t = Thread.currentThread();
System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted());
//待決中斷,中斷自身
t.interrupt();
System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted());
System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted());

try{
Thread.sleep(2000);
System.out.println("was NOT interrupted");
}catch( InterruptedException x){
System.out.println("was interrupted");
}
//丟擲異常後,會清除中斷標誌,這裡會返回false
System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted());
}
}
執行結果如下:

使用Thread.interrupted()方法判斷中斷狀態
可以使用Thread.interrupted()方法來檢查當前執行緒的中斷狀態(並隱式重置為false)。又由於它是靜態方法,因此不能在特定的執行緒上使用,而只能報告呼叫它的執行緒的中斷狀態,如果執行緒被中斷,而且中斷狀態尚不清楚,那麼,這個方法返回true。與isInterrupted()不同,它將自動重置中斷狀態為false,第二次呼叫Thread.interrupted()方法,總是返回false,除非中斷了執行緒。

如下程式碼演示了Thread.interrupted()方法的使用:

public class InterruptReset extends Object {
public static void main(String[] args) {
System.out.println(
"Point X: Thread.interrupted()=" + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println(
"Point Y: Thread.interrupted()=" + Thread.interrupted());
System.out.println(
"Point Z: Thread.interrupted()=" + Thread.interrupted());
}
}

執行結果如下:

從結果中可以看出,當前執行緒中斷自身後,在Y點,中斷狀態為true,並由Thread.interrupted()自動重置為false,那麼下次呼叫該方法得到的結果便是false。

補充
這裡補充下yield和join方法的使用。

join方法用執行緒物件呼叫,如果在一個執行緒A中呼叫另一個執行緒B的join方法,執行緒A將會等待執行緒B執行完畢後再執行。

yield可以直接用Thread類呼叫,yield讓出CPU執行權給同等級的執行緒,如果沒有相同級別的執行緒在等待CPU的執行權,則該執行緒繼續執行。