線程的啟動(dòng)很簡(jiǎn)單,但用戶可能隨時(shí)取消任務(wù)乾忱,怎么樣讓跑起來的線程正確地結(jié)束泪电,這是今天要討論的話題空盼。
使用標(biāo)志位
很簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志位,名稱就叫做isCancelled必孤。啟動(dòng)線程后,定期檢查這個(gè)標(biāo)志位。如果isCancelled=true仪召,那么線程就馬上結(jié)束。
public class MyThread implements Runnable{
private volatile boolean isCancelled;
public void run(){
while(!isCancelled){
//do something
}
}
public void cancel(){ isCancelled=true; }
}
注意的是松蒜,isCancelled需要為volatile扔茅,保證線程讀取時(shí)isCancelled是最新數(shù)據(jù)。
我以前經(jīng)常用這種簡(jiǎn)單方法秸苗,在大多時(shí)候也很有效召娜,但并不完善【ィ考慮下玖瘸,如果線程執(zhí)行的方法被阻塞,那么如何執(zhí)行isCancelled的檢查呢檀咙?線程有可能永遠(yuǎn)不會(huì)去檢查標(biāo)志位雅倒,也就卡住了。
使用中斷
Java提供了中斷機(jī)制弧可,Thread類下有三個(gè)重要方法蔑匣。
- public void interrupt()
- public boolean isInterrupted()
- public static boolean interrupted(); // 清除中斷標(biāo)志,并返回原狀態(tài)
每個(gè)線程都有個(gè)boolean類型的中斷狀態(tài)棕诵。當(dāng)使用Thread的interrupt()方法時(shí)裁良,線程的中斷狀態(tài)會(huì)被設(shè)置為true。
下面的例子啟動(dòng)了一個(gè)線程校套,循環(huán)執(zhí)行打印一些信息价脾。使用isInterrupted()方法判斷線程是否被中斷,如果是就結(jié)束線程笛匙。
public class InterruptedExample {
public static void main(String[] args) throws Exception {
InterruptedExample interruptedExample = new InterruptedExample();
interruptedExample.start();
}
public void start() {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(3000);
myThread.cancel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class MyThread extends Thread{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("test");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
//拋出InterruptedException后中斷標(biāo)志被清除侨把,標(biāo)準(zhǔn)做法是再次調(diào)用interrupt恢復(fù)中斷
Thread.currentThread().interrupt();
}
}
System.out.println("stop");
}
public void cancel(){
interrupt();
}
}
}
對(duì)線程調(diào)用interrupt()方法,不會(huì)真正中斷正在運(yùn)行的線程膳算,只是發(fā)出一個(gè)請(qǐng)求座硕,由線程在合適時(shí)候結(jié)束自己。
例如Thread.sleep這個(gè)阻塞方法涕蜂,接收到中斷請(qǐng)求华匾,會(huì)拋出InterruptedException,讓上層代碼處理。這個(gè)時(shí)候蜘拉,你可以什么都不做萨西,但等于吞掉了中斷。因?yàn)閽伋鯥nterruptedException后旭旭,中斷標(biāo)記會(huì)被重新設(shè)置為false谎脯!看sleep()的注釋,也強(qiáng)調(diào)了這點(diǎn)持寄。
@throws InterruptedException
if any thread has interrupted the current thread.
The interrupted status of the current thread is
cleared when this exception is thrown.
public static native void sleep(long millis) throws InterruptedException;
記得這個(gè)規(guī)則:什么時(shí)候都不應(yīng)該吞掉中斷源梭!每個(gè)線程都應(yīng)該有合適的方法響應(yīng)中斷!
所以在InterruptedExample例子里稍味,在接收到中斷請(qǐng)求時(shí)废麻,標(biāo)準(zhǔn)做法是執(zhí)行Thread.currentThread().interrupt()恢復(fù)中斷,讓線程退出模庐。
從另一方面談起烛愧,你不能吞掉中斷,也不能中斷你不熟悉的線程掂碱。如果線程沒有響應(yīng)中斷的方法怜姿,你無論調(diào)用多少次interrupt()方法,也像泥牛入海疼燥。
用Java庫的方法比自己寫的要好
自己手動(dòng)調(diào)用interrupt()方法來中斷程序沧卢,OK。但是Java庫提供了一些類來實(shí)現(xiàn)中斷悴了,更好更強(qiáng)大搏恤。
Executor框架提供了Java線程池的能力违寿,ExecutorService擴(kuò)展了Executor湃交,提供了管理線程生命周期的關(guān)鍵能力。其中藤巢,ExecutorService.submit返回了Future對(duì)象來描述一個(gè)線程任務(wù)搞莺,它有一個(gè)cancel()方法。
下面的例子擴(kuò)展了上面的InterruptedExample掂咒,要求線程在限定時(shí)間內(nèi)得到結(jié)果才沧,否則觸發(fā)超時(shí)停止。
public class InterruptByFuture {
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newSingleThreadExecutor();
Future<?> task = es.submit(new MyThread());
try {
//限定時(shí)間獲取結(jié)果
task.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
//超時(shí)觸發(fā)線程中止
System.out.println("thread over time");
} catch (ExecutionException e) {
throw e;
} finally {
boolean mayInterruptIfRunning = true;
task.cancel(mayInterruptIfRunning);
}
}
private static class MyThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("count");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
Thread.currentThread().interrupt();
}
}
System.out.println("thread stop");
}
public void cancel() {
interrupt();
}
}
}
Future的get方法可以傳入時(shí)間绍刮,如果限定時(shí)間內(nèi)沒有得到結(jié)果温圆,將會(huì)拋出TimeoutException。此時(shí)孩革,可以調(diào)用Future的cancel()方法岁歉,對(duì)任務(wù)所在線程發(fā)出中斷請(qǐng)求。
cancel()有個(gè)參數(shù)mayInterruptIfRunning膝蜈,表示任務(wù)是否能夠接收到中斷锅移。
- mayInterruptIfRunning=true時(shí)熔掺,任務(wù)如果在某個(gè)線程中運(yùn)行,那么這個(gè)線程能夠被中斷非剃;
- mayInterruptIfRunning=false時(shí)置逻,任務(wù)如果還未啟動(dòng),就不要運(yùn)行它备绽,應(yīng)用于不處理中斷的任務(wù)
要注意券坞,mayInterruptIfRunning=true表示線程能接收中斷,但線程是否實(shí)現(xiàn)了中斷不得而知肺素。線程要正確響應(yīng)中斷报慕,才能真正被cancel。
線程池的shutdownNow()會(huì)嘗試停止池內(nèi)所有在執(zhí)行的線程压怠,原理也是發(fā)出中斷請(qǐng)求眠冈。對(duì)于線程池的停止,下次新開一篇再講吧菌瘫。