取消任務(wù)的方式
Java中沒(méi)有提供任何機(jī)制來(lái)安全地終止線程,但是提供了中斷(Interruption)協(xié)作機(jī)制,能夠使一個(gè)線程終止另一個(gè)線程的當(dāng)前工作. 一般取消或停止某個(gè)任務(wù),很少采用立即停止,因?yàn)榱⒓赐V箷?huì)使得共享數(shù)據(jù)結(jié)構(gòu)出于不一致的狀態(tài).這也是Thread.stop(),Thread.suspend()以及Thread.resume()不安全的原因而廢棄.
Java中有三種方式可以終止當(dāng)前運(yùn)行的線程:
- 設(shè)置某個(gè)"已請(qǐng)求取消(Cancellation Requested)"標(biāo)記,而任務(wù)將定期查看該標(biāo)記的協(xié)作機(jī)制來(lái)中斷線程.
- 使用Thread.stop()強(qiáng)制終止線程,但是因?yàn)檫@個(gè)方法"解鎖"導(dǎo)致共享數(shù)據(jù)結(jié)構(gòu)處于不一致而不安全被廢棄.
- 使用Interruption中斷機(jī)制.
使用中斷標(biāo)記來(lái)中斷線程.
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使用一種簡(jiǎn)單取消策略,客戶(hù)端代碼通過(guò)調(diào)研cancel來(lái)請(qǐng)求取消,PrimeGenerator在每次搜索素?cái)?shù)時(shí)前先檢查是否存在取消請(qǐng)求,如果不存在就退出.
但是使用設(shè)置標(biāo)記的中斷策略有一問(wèn)題: 如果任務(wù)調(diào)用調(diào)用阻塞的方法,比如BlockingQueue.put,那么可能任務(wù)永遠(yuǎn)不會(huì)檢查取消標(biāo)記而不會(huì)結(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)
//此處阻塞,可能永遠(yuǎn)無(wú)法檢測(cè)到結(jié)束的標(biāo)記
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
}
}
public void cancel() {
cancelled = true;
}
}
解決辦法也很簡(jiǎn)單: 使用中斷而不是使用boolean標(biāo)記來(lái)請(qǐng)求取消
使用中斷(Interruption)請(qǐng)求取消
- Thread類(lèi)中的中斷方法:
-
public void interrupt()
請(qǐng)求中斷,設(shè)置中斷標(biāo)記,而并不是真正中斷一個(gè)正在運(yùn)行的線程,只是發(fā)出了一個(gè)請(qǐng)求中斷的請(qǐng)求,由線程在合適的時(shí)候中斷自己.
-
public static native boolean interrupted()
;判斷線程是否中斷,會(huì)擦除中斷標(biāo)記(判斷的是當(dāng)前運(yùn)行的線程),另外若調(diào)用Thread.interrupted()返回為true時(shí),必須要處理,可以拋出中斷異嘲简冢或者再次調(diào)用interrupt()來(lái)恢復(fù)中斷.
-
public native boolean isInterrupted()
;判斷線程是否中斷,不會(huì)擦除中斷標(biāo)記
-
故而上面問(wèn)題的解決方案如下:
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)用后到底意味著什么?
首先一個(gè)線程不應(yīng)該由其他線程來(lái)強(qiáng)制中斷或者停止,而應(yīng)該由線程自己停止,所以Thread.stop(),Thread.suspend(),Thread.resume()都已被廢棄.而{@link Thread#interrupt}作用其實(shí)不是中斷線程,而請(qǐng)求線程中斷.具體來(lái)說(shuō),當(dāng)調(diào)用interrupt()方法時(shí):
- 如果線程處于阻塞狀態(tài)時(shí)(例如處于sleep,wait,join等狀態(tài)時(shí))那么線程將立即退出阻塞狀態(tài)而拋出InterruptedException異常.
- 如果 線程處于正呈阆撸活動(dòng)狀態(tài),那么會(huì)將線程的中斷標(biāo)記設(shè)置為true,僅此而已.被設(shè)置中斷標(biāo)記的線程將繼續(xù)運(yùn)行而不受影響.
interrupt()并不能真正的中斷線程,需要被調(diào)用的線程自己進(jìn)行配合才行:
- 在正常運(yùn)行任務(wù)時(shí),經(jīng)常檢查本線程的中斷標(biāo)志位痛阻,如果被設(shè)置了中斷標(biāo)志就自行停止線程老充。
- 在調(diào)用阻塞方法時(shí)正確處理InterruptedException異常。