序
本文展示一個常見的取消線程的方法。
錯誤實例
class BrokenPrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
private volatile boolean cancelled = false;
BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!cancelled){
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException consumed) {
}
}
public void cancel() {
cancelled = true;
}
}
這里試圖用一個標志來跳出while循環,理論上貌似可行,但是這里使用的是阻塞的操作,那么就出現一種場景,線程永遠阻塞在put方法,根本就沒來得及下個循環去判斷cancelled這個條件,造成永遠無法停止掉線程。
正確方法
通過中斷來取消線程。
public class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted()){
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() {
interrupt();
}
}
這里的關鍵是queue的put操作能夠響應interrupt方法,拋出InterruptedException,倒不是因為while條件里頭的isInterrupted,這里while條件換成boolean可以照樣可以。
小結
調用interrupt并不意味著立即停止目標線程正在進行的工作,而只是傳遞了請求中斷的消息。對中斷操作的正確理解是:它并不會真正地中斷一個正在運行的線程,而只是發出中斷請求,然后由線程在下一個合適的時刻中斷自己。
有些方法,例如wait、sleep和join等,將嚴格地處理這種請求,當它們收到中斷請求或者在開始執行時發現某個已被設置好的中斷狀態時,將拋出一個異常。設計良好的方法可以完全忽略這種請求,只要它們能使調用代碼對中斷請求進行某種處理。
設計糟糕的方法可能會屏蔽中斷請求,從而導致調用棧中的其他代碼無法對中斷請求作出響應。在使用靜態的interrupted時應該小心,因為它會清除當前線程的中斷狀態。如果在調用interrupted時返回了true,那么除非你想屏蔽這個中斷,否則必須對它進行處理—可以拋出InterruptedException,或者通過再次調用interrupt來恢復中斷狀態。