取消任務(wù)的方式
Java中沒有提供任何機制來安全地終止線程,但是提供了中斷(Interruption)協(xié)作機制,能夠使一個線程終止另一個線程的當(dāng)前工作. 一般取消或停止某個任務(wù),很少采用立即停止,因為立即停止會使得共享數(shù)據(jù)結(jié)構(gòu)出于不一致的狀態(tài).這也是Thread.stop(),Thread.suspend()以及Thread.resume()不安全的原因而廢棄.
Java中有三種方式可以終止當(dāng)前運行的線程:
- 設(shè)置某個"已請求取消(Cancellation Requested)"標(biāo)記,而任務(wù)將定期查看該標(biāo)記的協(xié)作機制來中斷線程.
- 使用Thread.stop()強制終止線程,但是因為這個方法"解鎖"導(dǎo)致共享數(shù)據(jù)結(jié)構(gòu)處于不一致而不安全被廢棄.
- 使用Interruption中斷機制.
使用中斷標(biāo)記來中斷線程.
public class PrimeGenerator implements Runnable {
private static ExecutorService exec = Executors.newCachedThreadPool();
@GuardedBy("this") private final List<BigInteger> primes
= new ArrayList<BigInteger>();
private volatile boolean cancelled;
public void run() {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
p = p.nextProbablePrime();
synchronized (this) {
primes.add(p);
}
}
}
public void cancel() {
cancelled = true;
}
public synchronized List<BigInteger> get() {
return new ArrayList<BigInteger>(primes);
}
static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
exec.execute(generator);
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
}
設(shè)置標(biāo)記的中斷策略: PrimeGenerator使用一種簡單取消策略,客戶端代碼通過調(diào)研cancel來請求取消,PrimeGenerator在每次搜索素數(shù)時前先檢查是否存在取消請求,如果不存在就退出.
但是使用設(shè)置標(biāo)記的中斷策略有一問題: 如果任務(wù)調(diào)用調(diào)用阻塞的方法,比如BlockingQueue.put,那么可能任務(wù)永遠不會檢查取消標(biāo)記而不會結(jié)束.
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)
//此處阻塞,可能永遠無法檢測到結(jié)束的標(biāo)記
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
}
}
public void cancel() {
cancelled = true;
}
}
解決辦法也很簡單: 使用中斷而不是使用boolean標(biāo)記來請求取消
使用中斷(Interruption)請求取消
- Thread類中的中斷方法:
-
public void interrupt()請求中斷,設(shè)置中斷標(biāo)記,而并不是真正中斷一個正在運行的線程,只是發(fā)出了一個請求中斷的請求,由線程在合適的時候中斷自己.
-
public static native boolean interrupted();判斷線程是否中斷,會擦除中斷標(biāo)記(判斷的是當(dāng)前運行的線程),另外若調(diào)用Thread.interrupted()返回為true時,必須要處理,可以拋出中斷異?;蛘咴俅握{(diào)用interrupt()來恢復(fù)中斷.
-
public native boolean isInterrupted();判斷線程是否中斷,不會擦除中斷標(biāo)記
-
故而上面問題的解決方案如下:
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();
}
}
-
那么thread.interrupt()調(diào)用后到底意味著什么?
首先一個線程不應(yīng)該由其他線程來強制中斷或者停止,而應(yīng)該由線程自己停止,所以Thread.stop(),Thread.suspend(),Thread.resume()都已被廢棄.而{@link Thread#interrupt}作用其實不是中斷線程,而請求線程中斷.具體來說,當(dāng)調(diào)用interrupt()方法時:
- 如果線程處于阻塞狀態(tài)時(例如處于sleep,wait,join等狀態(tài)時)那么線程將立即退出阻塞狀態(tài)而拋出InterruptedException異常.
- 如果 線程處于正?;顒訝顟B(tài),那么會將線程的中斷標(biāo)記設(shè)置為true,僅此而已.被設(shè)置中斷標(biāo)記的線程將繼續(xù)運行而不受影響.
interrupt()并不能真正的中斷線程,需要被調(diào)用的線程自己進行配合才行:
- 在正常運行任務(wù)時,經(jīng)常檢查本線程的中斷標(biāo)志位,如果被設(shè)置了中斷標(biāo)志就自行停止線程。
- 在調(diào)用阻塞方法時正確處理InterruptedException異常。