線程的中斷
使用interrupt()中斷線程
當(dāng)一個(gè)線程運(yùn)行時(shí),另一個(gè)線程可以調(diào)用對(duì)應(yīng)的Thread對(duì)象的interrupt()方法來中斷它,該方法只是在目標(biāo)線程中設(shè)置一個(gè)標(biāo)志,表示它已經(jīng)被中斷,并立即返回。這里需要注意的是,如果只是單純的調(diào)用interrupt()方法,線程并沒有實(shí)際被中斷,會(huì)繼續(xù)往下執(zhí)行。
下面一段代碼演示了休眠線程的中斷:
public class SleepInterrupt 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,線程不會(huì)實(shí)際被中斷,它會(huì)繼續(xù)打印下面的信息
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秒,從而確保剛才啟動(dòng)的線程有機(jī)會(huì)執(zhí)行一段時(shí)間
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");
}
}
運(yùn)行結(jié)果如下:

主線程啟動(dòng)新線程后,自身休眠2秒鐘,允許新線程獲得運(yùn)行時(shí)間。新線程打印信息“about to sleep for 20 seconds”后,繼而休眠20秒鐘,大約2秒鐘后,main線程通知新線程中斷,那么新線程的20秒的休眠將被打斷,從而拋出InterruptedException異常,執(zhí)行跳轉(zhuǎn)到catch塊,打印出“interrupted while sleeping”信息,并立即從run()方法返回,然后消亡,而不會(huì)打印出catch塊后面的“l(fā)eaving normally”信息。
請(qǐng)注意:由于不確定的線程規(guī)劃,上圖運(yùn)行結(jié)果的后兩行可能順序相反,這取決于主線程和新線程哪個(gè)先消亡。但前兩行信息的順序必定如上圖所示。
另外,如果將catch塊中的return語句注釋掉,則線程在拋出異常后,會(huì)繼續(xù)往下執(zhí)行,而不會(huì)被中斷,從而會(huì)打印出”leaving normally“信息。
待決中斷
在上面的例子中,sleep()方法的實(shí)現(xiàn)檢查到休眠線程被中斷,它會(huì)相當(dāng)友好地終止線程,并拋出InterruptedException異常。另外一種情況,如果線程在調(diào)用sleep()方法前被中斷,那么該中斷稱為待決中斷,它會(huì)在剛調(diào)用sleep()方法時(shí),立即拋出InterruptedException異常。
下面的代碼演示了待決中斷:
public class PendingInterrupt {
public static void main(String[] args) {
Thread.currentThread().interrupt();
try {
Thread.sleep(2000);
System.out.println("was NOT interrupted");
} catch (InterruptedException x) {
System.out.println("was interrupted");
}
}
}

這種模式下,main線程中斷它自身。除了將中斷標(biāo)志(它是Thread的內(nèi)部標(biāo)志)設(shè)置為true外,沒有其他任何影響。線程被中斷了,但main線程仍然運(yùn)行,main線程進(jìn)入try塊,一旦調(diào)用sleep()方法,它就會(huì)注意到待決中斷的存在,并拋出InterruptedException。于是執(zhí)行跳轉(zhuǎn)到catch塊,并打印出線程被中斷的信息。
使用isInterrupted()方法判斷中斷狀態(tài)
可以在Thread對(duì)象上調(diào)用isInterrupted()方法來檢查任何線程的中斷狀態(tài)。這里需要注意:線程一旦被中斷,isInterrupted()方法便會(huì)返回true,而一旦sleep()方法拋出異常,它將清空中斷標(biāo)志,此時(shí)isInterrupted()方法將返回false。
下面的代碼演示了isInterrupted()方法的使用:
public class InterruptCheck {
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");
}
// 拋出異常后,會(huì)清除中斷標(biāo)志,這里會(huì)返回false
System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted());
}
}
執(zhí)行結(jié)果如下:

使用Thread.interrupted()方法判斷中斷狀態(tài)
可以使用靜態(tài)的Thread.interrupted()方法來檢查當(dāng)前線程的中斷狀態(tài)(并隱式重置為false)。又由于它是靜態(tài)方法,因此不能在特定的線程上使用,而只能報(bào)告調(diào)用它的線程的中斷狀態(tài),如果線程被中斷,而且中斷狀態(tài)尚不清楚,那么,這個(gè)方法返回true。與isInterrupted()不同,它將自動(dòng)重置中斷狀態(tài)為false,第二次調(diào)用Thread.interrupted()方法,總是返回false,除非中斷了線程。
如下代碼演示了Thread.interrupted()方法的使用:
public class InterruptReset {
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ùn)行結(jié)果如下:

從結(jié)果中可以看出,當(dāng)前線程中斷自身后,在Y點(diǎn),中斷狀態(tài)為true,并由Thread.interrupted()自動(dòng)重置為false,那么下次調(diào)用該方法得到的結(jié)果便是false。
線程中斷(摘自 Java并發(fā)編程實(shí)戰(zhàn) )
線程中斷是一種協(xié)作機(jī)制,線程可以通過這種機(jī)制來通知另一個(gè)線程,告訴它在合適的或者可能的情況下停止當(dāng)前工作,并轉(zhuǎn)而執(zhí)行其他的工作。
每個(gè)線程都有一個(gè)boolean類型的中斷狀態(tài)。當(dāng)中斷線程時(shí),這個(gè)線程的中斷狀態(tài)將被設(shè)置為true。在Thread中包含了中斷線程以及查詢線程中斷狀態(tài)的方法,如以下代碼所示。interrupt方法能中斷目標(biāo)線程,而isInterrupted方法能返回目標(biāo)線程的中斷狀態(tài)。靜態(tài)的interrupted方法將清除當(dāng)前線程的中斷狀態(tài),并返回它之前的值,這也是清除中斷狀態(tài)的唯一方法。
public class Thread {
public void interrupt() {...}
public boolean isInterrupted() {...}
public static boolean interrupted() {...}
...
}
阻塞庫(kù)方法,例如Thread.sleep、BlockingQueue.put和Object.wait等,都會(huì)檢查線程何時(shí)中斷,并且在發(fā)現(xiàn)中斷時(shí)提前返回。它們?cè)陧憫?yīng)中斷時(shí)執(zhí)行的操作包括:清除中斷狀態(tài),拋出InterruptedException,表示阻塞操作由于中斷而提前結(jié)束。JVM并不能保證阻塞方法檢測(cè)到中斷的速度,但在實(shí)際情況中響應(yīng)速度還是非??斓摹?/p>
當(dāng)線程在非阻塞狀態(tài)下中斷時(shí),它的中斷狀態(tài)將被設(shè)置,然后根據(jù)將被取消的操作來檢查中斷狀態(tài)以判斷發(fā)生了中斷。通過這樣的方法,中斷操作將變得“有黏性”——如果不觸發(fā)InterruptedException,那么中斷狀態(tài)將一直保持,直到明確地清除中斷狀態(tài)。
調(diào)用interrupt 并不意味著立即停止目標(biāo)線程正在進(jìn)行的工作,而只是傳遞了請(qǐng)求中斷的消息。
對(duì)中斷操作的正確理解是:它并不會(huì)真正地中斷一個(gè)正在運(yùn)行的線程,而只是發(fā)出中斷請(qǐng)求,然后由線程在下一個(gè)合適的時(shí)刻中斷自己。(這些時(shí)刻也被稱為取消點(diǎn))。有些方法,例如wai、sleep和join等,將嚴(yán)格地處理這種請(qǐng)求,當(dāng)它們收到中斷請(qǐng)求或者在開始執(zhí)行時(shí)發(fā)現(xiàn)某個(gè)已被設(shè)置好的中斷狀態(tài)時(shí),將拋出一個(gè)異常。設(shè)計(jì)良好的方法可以完全忽略這種請(qǐng)求,只要它們能使調(diào)用代碼對(duì)中斷請(qǐng)求進(jìn)行某種處理。設(shè)計(jì)糟糕的方法可能會(huì)屏蔽中斷請(qǐng)求,從而導(dǎo)致調(diào)用棧中的其它代碼無法對(duì)中斷請(qǐng)求作出響應(yīng)。
在使用靜態(tài)的interrupted時(shí)應(yīng)該小心,因?yàn)樗鼤?huì)清除當(dāng)前線程的中斷狀態(tài)。如果在調(diào)用interrupted時(shí)返回了true,那么除非你想屏蔽這個(gè)中斷,否則必須對(duì)它進(jìn)行處理——可以拋出InterruptedException,或者通過再次調(diào)用interrupt來恢復(fù)中斷狀態(tài)。
如何處理中斷異常(InterruptedException)
中斷是一種協(xié)作機(jī)制。一個(gè)線程不能強(qiáng)制其它線程停止正在執(zhí)行的操作而去執(zhí)行其它的操作。當(dāng)線程A中斷B時(shí),A僅僅是要求B在執(zhí)行到某個(gè)可以暫停的地方停止正在執(zhí)行的操作——前提是如果線程B愿意停止下來。雖然在API或者語言規(guī)范中并沒有為中斷定義任何特定應(yīng)用級(jí)別的語義,但最常使用的中斷的情況就是取消某個(gè)操作。方法對(duì)中斷請(qǐng)求的響應(yīng)度越高,就越容易及時(shí)取消那些執(zhí)行時(shí)間很長(zhǎng)的操作。
當(dāng)在代碼中調(diào)用了一個(gè)將拋出InterruptedException異常的方法時(shí),你自己的方法也就變成了一個(gè)阻塞方法,并且必須要處理對(duì)中斷的響應(yīng)。有兩種實(shí)用策略可用于處理InterruptedException:
- 傳遞InterruptedException
避開這個(gè)異常通常是最明智的策略——只需把InterruptedException傳遞給方法的調(diào)用者。傳遞InterruptedException的方法包括,根本不捕獲該異常,或者捕獲該異常,然后在執(zhí)行某種簡(jiǎn)單的清理工作后再次拋出這個(gè)異常。
// 將InterruptedException傳遞給調(diào)用者
BlockingQueue<Task> queue;
...
public Task getNextTask() throws InterruptedException {
return queue.take();
}
- 恢復(fù)中斷
有時(shí)候不能拋出InterruptedException,例如當(dāng)代碼是Runnable的一部分時(shí),在這些情況下,必須捕獲InterruptedException,并通過調(diào)用當(dāng)前線程上的interrupt方法恢復(fù)中斷狀態(tài),這樣在調(diào)用棧中更高層的代碼將看到引發(fā)了一個(gè)中斷。你不能屏蔽InterruptedException,例如在catch塊中捕獲到異常卻不做任何處理,除非在你的代碼中實(shí)現(xiàn)了線程的中斷策略。
// 恢復(fù)中斷狀態(tài)以避免屏蔽中斷
public class TaskRunnable implements Runnable {
BlockingQueue<Task> queue;
...
public void run() {
try {
processTask(queue.take());
} catch(InterruptedException e) {
// 恢復(fù)被中斷的狀態(tài)
Thread.currentThread().interrupt();
}
}
}
處理不可中斷的阻塞
在Java庫(kù)中,許多可阻塞的方法都是通過提前返回或者拋出InterruptedException來響應(yīng)中斷請(qǐng)求的,從而使開發(fā)人員更容易構(gòu)建出能響應(yīng)取消請(qǐng)求的任務(wù)。然而,并非所有的可阻塞方法或者阻塞機(jī)制都能響應(yīng)中斷;如果一個(gè)線程由于執(zhí)行同步的Socket I/O或者等待獲得內(nèi)置鎖而阻塞,那么中斷請(qǐng)求只能設(shè)置線程的中斷狀態(tài),除此之外沒有其他任何作用。
- Java.io包中的同步Socket I/O
在服務(wù)器應(yīng)用程序中,最常見的阻塞I/O形式就是對(duì)套接字進(jìn)行讀取和寫入。雖然InputStream和OutputStream中的read和write等方法都不會(huì)響應(yīng)中斷,但通過關(guān)閉底層的套接字,可以使得由于執(zhí)行read和write等方法而被阻塞的線程拋出一個(gè)SocketException。
- Selector的異步I/O
如果一個(gè)線程在調(diào)用Selector.select方法(在java.nio.channels中)時(shí)阻塞了,那么調(diào)用close或wakeup方法會(huì)使線程拋出ClosedSelectorException并提前返回。
- 獲取某個(gè)鎖
如果一個(gè)線程由于等待某個(gè)內(nèi)置鎖而阻塞,那么將無法響應(yīng)中斷,因?yàn)榫€程認(rèn)為它肯定會(huì)獲得鎖,所以將不會(huì)理會(huì)中斷請(qǐng)求。但是,在Lock類中提供了lockInterruptibly方法,該方法允許在等待一個(gè)鎖的同時(shí)仍能響應(yīng)中斷。
處理不可中斷的阻塞可參考以下代碼:
// 通過改寫interrupt方法將非標(biāo)準(zhǔn)的取消操作封裝在Thread中
public class ReaderThread extends Thread {
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException {
this.socket = socket;
this.in = socket.getInputStream();
}
public void interrupt() {
try {
socket.close();
} catch(IOException ignored) {
} finally {
super.interrupt();
}
}
public void run() {
try {
byte[] buf = new byte[BUFSZ];
while(true) {
int count = in.read(buf);
if (count < 0)
break;
else if (count > 0)
processBuffer(buf, count);
}
} catch (IOException e) {
// 允許線程退出
}
}
}