為什么要中斷
- Java中沒有一種安全的搶占方法來停止線程槐臀,也沒有安全的搶占方式停止任務(wù),只有一些協(xié)作機制氓仲。
- 更好的支持任務(wù)的取消關(guān)閉
可以取消的任務(wù)
- 下面任務(wù)名稱:在規(guī)定時間內(nèi)搜索素數(shù)水慨。
- 任務(wù)特點:任務(wù)最終會結(jié)束。
- PrimeGenerator持續(xù)地枚舉素數(shù)敬扛,直到被取消晰洒。cancel放在finally中確保cancel最終會被調(diào)用。
- PrimeGenerator使用了一種簡單的取消策略:客戶端代碼通過cancel來請求取消啥箭,PrimeGenerator在每次搜索素數(shù)前首先檢查是否存在取消請求谍珊,如果存在則退出。
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); //缺點:如果generator拋出未受檢異常急侥,該異常在此處無法撲捉到
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
永遠不會結(jié)束的任務(wù)
- 下面任務(wù)名稱:生產(chǎn)者-消費者模式砌滞,生產(chǎn)者生產(chǎn)素數(shù),消費者消費素數(shù)坏怪,使用阻塞隊列贝润。
- 任務(wù)特點:存在無法結(jié)束情況。
- 同樣通過cancelled來作為取消標(biāo)志铝宵,但是run任務(wù)中存在阻塞方法put,如果生產(chǎn)者速度超過消費者速度打掘,隊列被填滿,put方法阻塞,當(dāng)put方法阻塞時胧卤,如果消費者希望取消生產(chǎn)者任務(wù)唯绍,那么消費者可以調(diào)用cancel來設(shè)置cancelled標(biāo)志,但是此時生產(chǎn)者卻永遠不能檢查這個標(biāo)志枝誊,因為它無法從阻塞的put方法中恢復(fù)過來况芒。
- 說明了自定義的取消機制無法與可阻塞的庫函數(shù)實現(xiàn)良好的交互。
- 解決方法:使用中斷而不是boolean標(biāo)志來請求取消叶撒。(見下面代碼)
public 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;
}
void consumePrimes(){
BlockingQueue<BigInteger> primes = new LinkedBlockingQueue<BigInteger>(3);
BrokenPrimeProducer primeProducer = new BrokenPrimeProducer(primes);
primeProducer.start(); //缺點:如果generator拋出未受檢異常绝骚,該異常在此處無法撲捉到
try{
while(needMorePrimes()){
consume(primes.take());
}
}finally {
primeProducer.cancel();;
}
}
}
Thread中的中斷方法
每個線程都有一個boolean類型的中斷狀態(tài),當(dāng)中斷線程時祠够,這個線程的中斷狀態(tài)將被設(shè)置為true压汪。線程可以查詢中斷狀態(tài)根據(jù)需求來做響應(yīng)處理。
interrupt :中斷目標(biāo)線程
isInterrupted:返回目標(biāo)線程的中斷狀態(tài)
interrupted (靜態(tài)方法):清除當(dāng)前線程的中斷狀態(tài)古瓤,并返回它之前的值止剖,這是清除中斷狀態(tài)的唯一方法。
中斷不會真正中斷一個正在運行的線程落君,只是發(fā)出中斷請求穿香,然后由線程在下一個合適的時刻中斷自己。
阻塞方法如何處理中斷
- 阻塞庫方法绎速,如Thread.sleep皮获、Object.wait,阻塞隊列的take纹冤、put方法等等洒宝,都會檢查當(dāng)前線程中斷狀態(tài)(即執(zhí)行該代碼的線程),并且在發(fā)現(xiàn)中斷時提前返回萌京。它們在響應(yīng)中斷時執(zhí)行的操作包括:清除中斷狀態(tài)奖蔓,拋出InterruptedException壳快,表示阻塞操作由于中斷而提前結(jié)束硼婿。
- 注:并不是所有的阻塞方法都會通過提前返回或拋出異常來響應(yīng)中斷請求(如Socket I/O)
- JVM并不能保證阻塞方法檢測到中斷的速度辜御,但在實際情況中響應(yīng)速度還非橙鲶埃快的藐守。
如何處理阻塞方法拋出的InterruptedException異常
- 1.傳遞InterruptedException:只需把InterruptedException傳遞給調(diào)用方法的調(diào)用者荸频。傳遞包括:根本不捕獲該異常留晚,通過throws拋印蔗“亲睿或者捕獲異常,然后在執(zhí)行某種清理工作后再次拋出這個異常华嘹。
- 2.恢復(fù)中斷:有時候不能拋出InterruptedException吧趣,例如代碼是Runnable的一部分,這時必須捕獲異常,并通過調(diào)用當(dāng)前線程上的interrupt方法恢復(fù)中斷狀態(tài)(阻塞方法處理中斷會清除中斷狀態(tài))强挫,這樣調(diào)用棧中更高層代碼將看到引發(fā)了一個中斷岔霸。(只有調(diào)用isInterrupted才可以看到)。
public class TaskRunnable implements Runnable {
BlockingQueue<Task> queue;
public void run() {
try {
processTask(queue.take());
} catch (InterruptedException e) {
// 恢復(fù)中斷狀態(tài)
Thread.currentThread().interrupt();
}
}
void processTask(Task task) {
// Handle the task
}
interface Task {
}
}
- 3.屏蔽中斷:只有一種情況才可以屏蔽中斷俯渤,只有實現(xiàn)了線程中斷策略的代碼才可以屏蔽中斷請求呆细,在常規(guī)的任務(wù)和代碼庫中都不應(yīng)該屏蔽中斷請求。(見下面PrimeProducer類的代碼)
- 4.其它方法八匠,根據(jù)需求定絮爷。
- 下面代碼的演示了不可以取消的任務(wù),在發(fā)生中斷時該如何響應(yīng)中斷情況梨树,代碼中有阻塞方法take坑夯,如果線程被中斷就會使take拋出InterruptedException異常,但是任務(wù)不可以取消抡四,所以發(fā)生中斷后通過重試
public class NoncancelableTask {
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
// fall through and retry
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
interface Task {
}
}
使用中斷解決上面“永遠不會結(jié)束的任務(wù)”
- 雖然PrimeProducer屏蔽了中斷柜蜈,這是因為它知道線程將要結(jié)束(通過while (!Thread.currentThread().isInterrupted())代碼),即PrimeProducer實現(xiàn)了線程的中斷策略指巡,所以線程被中斷后淑履,雖然put方法會拋出中斷異常,但是那是合理的厌处,因為即使不拋出異常鳖谈,while方法中通過檢查也會發(fā)現(xiàn)線程被中斷,從而結(jié)束線程阔涉。
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) {
/* 允許線程退出 */
}
}
public void cancel() {
interrupt();
}
}
使用Future來取消任務(wù)
- 針對前面的案例缆娃,如果在run中的任務(wù)發(fā)生了未檢查異常,則該對于線程調(diào)用者來說該異常會被忽略掉瑰排,因為其運行在單獨的線程中贯要,如果想將異常拋出以便調(diào)用者來處理,則該使用Future來處理椭住。
- Future的cancel方法:接收boolean類型參數(shù)mayInterrptIfRuning,表示任務(wù)是否能夠接收中斷崇渗,如果為true,并且任務(wù)當(dāng)前正在某個線程中運行京郑,那么這個線程能被中斷宅广。如果這個參數(shù)為false,那么“若任務(wù)還沒啟動些举,就不要運行”跟狱。
- 除非你清楚線程中斷策略,否則不要中斷線程户魏,那么在什么情況下可以將cancel參數(shù)設(shè)為true?執(zhí)行任務(wù)的線程由標(biāo)準(zhǔn)的Executor創(chuàng)建的驶臊,它實現(xiàn)了一種中斷策略使得任務(wù)可以通過中斷被取消挪挤。
public class TimedRun {
private static final ExecutorService taskExec = Executors.newCachedThreadPool();
public static void timedRun(Runnable r,
long timeout, TimeUnit unit)
throws InterruptedException {
Future<?> task = taskExec.submit(r);
try {
task.get(timeout, unit);
} catch (TimeoutException e) {
// 接下來任務(wù)將被取消,放在了finally處理
} catch (ExecutionException e) {
// 如果在任務(wù)中拋出異常关翎,那么重新拋出該異常
throw launderThrowable(e.getCause());
} finally {
// Harmless if task already completed
task.cancel(true); // interrupt if running
}
}
public static RuntimeException launderThrowable(Throwable t) {
if (t instanceof RuntimeException)
return (RuntimeException) t;
else if (t instanceof Error)
throw (Error) t;
else
throw new IllegalStateException("Not unchecked", t);
}
}
中斷策略
- 中斷策略規(guī)定線程如何解釋某個中斷請求---當(dāng)發(fā)現(xiàn)中斷請求時扛门,應(yīng)該做哪些工作,哪些工作單元對于中斷來說是原子性操作纵寝,以及以多快的速度來響應(yīng)中斷论寨。