概念
- 中斷(Interruption),是一種協(xié)作機制革答,能夠使一個線程終止另一個線程的當(dāng)前工作。
- 中斷只適合用于取消操作参淫,如果用在取消之外的其他操作則是不合適的;中斷是實現(xiàn)取消的最合理方式愧杯。
中斷是必要的
- 我們很少希望某個任務(wù)涎才、線程或服務(wù)立即停止,因為這樣會使共享的數(shù)據(jù)結(jié)構(gòu)處于不一致的狀態(tài)力九;而使用中斷這種協(xié)作機制耍铜,當(dāng)需要停止時,任務(wù)首先會清除當(dāng)前正在執(zhí)行的工作畏邢,然后再結(jié)束业扒,因為任務(wù)本身的代碼比發(fā)出請求的代碼更清楚如何執(zhí)行清除工作。
行為好壞的程序的區(qū)別
- 行為良好的程序能很完善地處理失敗舒萎、關(guān)閉和取消等情況程储。
取消任務(wù)概念
- 外部代碼能在某個任務(wù)正常完成之前將其置為“完成”狀態(tài)
任務(wù)取消原因
- 用戶請求取消(例如:用戶點擊取消按鈕)
- 有時間限制的操作(例如:任務(wù)執(zhí)行超時蹭沛,則取消任務(wù))
- 應(yīng)用程序事件(例如:一個任務(wù)的完成事件,導(dǎo)致其他任務(wù)取消)
- 錯誤(例如:一個任務(wù)發(fā)生錯誤章鲤,導(dǎo)致其他任務(wù)取消)
- 關(guān)閉(例如:程序運行結(jié)束摊灭,取消長運行的任務(wù))
任務(wù)取消策略
- How:外部代碼如何請求取消任務(wù)
- When:任務(wù)在何時檢查外部代碼是否已經(jīng)請求了取消
- What:任務(wù)取消時應(yīng)該執(zhí)行哪些操作
線程中斷策略
- 中斷策略規(guī)定線程如何解釋某個中斷請求
- 最合理的中斷策略是某種線程級或服務(wù)級取消操作:
- 盡快退出或拋出InterruptException異常
- 在必要的時候進行清理(可以在線程中斷后進行清理工作,例如shutdown方法內(nèi)清理)
- 通知某個所有者該線程已經(jīng)退出(標記被中斷線程的狀態(tài)為中斷)
- 其他常用中斷策略:停止服務(wù)败徊、重新開始服務(wù)帚呼,對于包含這些非標準中斷策略的線程或線程池,只能應(yīng)用于知道這些策略的任務(wù)中皱蹦。
注:正如任務(wù)中應(yīng)該包含取消策略煤杀,線程同樣應(yīng)該包含中斷策略
Thread中斷相關(guān)操作
- interrupt: 用于中斷目標線程
- isInterrupted: 用于檢測目標線程是否會中斷
- Thread.interrupted: 用于清除當(dāng)前線程的中斷狀態(tài),并返回之前的中斷狀態(tài)沪哺,這是清除中斷狀態(tài)的唯一方法
注1:如果目標線程已經(jīng)結(jié)束沈自,則 isInterrupted 始終返回false!
注2:調(diào)用interrupt并不意味著立即停止目標線程正在執(zhí)行的任務(wù)辜妓,而只是傳遞了請求中斷信息枯途!
支持中斷的阻塞庫方法
- Thread.sleep
- Thread.join
- Object.wait
注:它們響應(yīng)中斷的操作包括:清除中斷狀態(tài),拋出InterruptedException籍滴。也就是說酪夷,收到InterruptedException時,中斷狀態(tài)已經(jīng)為 false 孽惰!
中斷請求的接收者
- 任務(wù)和線程都是中斷請求的接收者晚岭,一個中斷請求到達時,意味著需要“取消任務(wù)”和“關(guān)閉工作者線程”
- 任務(wù)不會在自己擁有的線程中運行(任務(wù)只是任務(wù)灰瞻,不是線程)腥例,任務(wù)是非線程所有者辅甥,在處理中斷請求時應(yīng)該小心的保存中斷狀態(tài)酝润,這樣線程擁有者才能對中斷做出響應(yīng),即使其他后續(xù)“非擁有者”也可以做出響應(yīng)璃弄。
任務(wù)響應(yīng)中斷的兩種方式
- 傳遞異常InterruptedException
- 恢復(fù)中斷狀態(tài)(調(diào)用Thread.interrupt方法)
注1:任務(wù)不應(yīng)該對執(zhí)行該任務(wù)的線程的中斷策略做出任何假設(shè)要销,除非該任務(wù)被專門設(shè)計為服務(wù)中運行,并且在這些服務(wù)中包含特定的中斷策略夏块!
注2:只有實現(xiàn)了線程中斷策略的代碼才可以屏蔽中斷請求疏咐,在常規(guī)的任務(wù)和庫代碼中都不應(yīng)該屏蔽中斷請求!
如何中斷線程
- 線程只能由其所有者中斷脐供,所有者可以將線程的中斷策略信息封裝到某個合適的取消機制中浑塞,里如果關(guān)閉(shutdown)方法。
任務(wù)對中斷的響應(yīng)
- 支持取消且調(diào)用了中斷阻塞方法的任務(wù)政己,應(yīng)該盡快取消任務(wù)酌壕,恢復(fù)中斷狀態(tài)
- 不支持取消但在循環(huán)中調(diào)用中斷阻塞方法的任務(wù),應(yīng)該在本地保存中斷狀態(tài),并在任務(wù)返回前恢復(fù)中斷狀態(tài)卵牍,而不是在捕獲中斷時就恢復(fù)果港,那樣容易引起死循環(huán)
- 不調(diào)用中斷阻塞方法的任務(wù),在循環(huán)中輪詢當(dāng)前線程的中斷狀態(tài)來響應(yīng)中斷
Java中斷機制的優(yōu)點
- Java中斷為非搶占式中斷糊昙,通過推遲中斷請求的處理辛掠,開發(fā)人員能指定靈活的終端策略,從而使應(yīng)用程序在響應(yīng)性和建壯性之間實現(xiàn)合理的平衡释牺。
標準取消操作示例
package cn.weicm.cancel;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* <b>Time:	2018/8/3 13:52</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	在外部線程中安排中斷</b><br/>
* <br/>
* <b>優(yōu)點:</b><br/>
* <ul>
* <li>能從任務(wù)中拋出未檢查異常萝衩,異常會被timedRun的調(diào)用者捕獲</li>
* </ul>
* <br/>
* <b>缺點:</b><br/>
* <ul>
* <li>在中斷線程時,不了解執(zhí)行任務(wù)的線程的中斷策略没咙,因為timedRun可能在任何線程中運行</li>
* <li>如果任務(wù)不響應(yīng)中斷欠气,那么timedRun會在任務(wù)結(jié)束時才返回钥弯,此時可能已經(jīng)超過了指定時限豹储,這會對調(diào)用者帶來負面影響</li>
* </ul>
*/
public class TimedRun1 {
private static final ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
public static void main(String[] args) {
try {
timedRun(() -> {
System.out.println("Task start ...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("Task is canceled!");
//假設(shè)任務(wù)不響應(yīng)中斷,即不拋出InterruptedException喳资,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)袁梗,而是直接退出
return;
}
System.out.println("Task end ...");
}, 1, TimeUnit.SECONDS);
} finally {
ses.shutdown();
}
}
/**
* <b>Time:	2018/8/3 14:20</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	在制定時限內(nèi)運行任務(wù)宜鸯,超過時限則取消任務(wù)</b><br/>
*
* @param task 任務(wù)
* @param timeout 時限數(shù)量
* @param unit 時限單位
*/
static void timedRun(Runnable task, long timeout, TimeUnit unit) {
//獲取目標任務(wù)所在線程,此時該線程可能是任意線程
Thread currentThread = Thread.currentThread();
//啟動中斷任務(wù)
ScheduledFuture<?> future = ses.schedule(() -> {
currentThread.interrupt();
}, timeout, unit);
task.run();
//任務(wù)結(jié)束后遮怜,取消掉中斷任務(wù)淋袖,因為此時目標任務(wù)已經(jīng)結(jié)束,中斷任務(wù)已經(jīng)沒有存在的意義了
future.cancel(true);
}
}
package cn.weicm.cancel;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* <b>Time:	2018/8/3 15:36</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	在專門的線程中中斷任務(wù)</b><br/>
* </br>
* <b>優(yōu)點:	</b><br/>
* <ul>
* <li>解決了TimedRun1的所有缺點</li>
* </ul>
* </br>
* <b>缺點:	</b><br/>
* <ul>
* <li>由于join的不足锯梁,無法知道任務(wù)是因為線程正常退出而返回還是因為join超時而返回</li>
* </ul>
*/
public class TimedRun2 {
private static final ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
public static void main(String[] args) throws Throwable {
try {
timedRun(() -> {
System.out.println("Task start ...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("Task is canceled!");
//假設(shè)任務(wù)不響應(yīng)中斷即碗,即不拋出InterruptedException,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)陌凳,而是直接退出
return;
}
System.out.println("Task end ...");
}, 1, TimeUnit.SECONDS);
} finally {
ses.shutdown();
}
}
/**
* <b>Time:	2018/8/3 14:20</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	在制定時限內(nèi)運行任務(wù)剥懒,超過時限則取消任務(wù)</b><br/>
*
* @param task 任務(wù)
* @param timeout 時限數(shù)量
* @param unit 時限單位
*/
static void timedRun(Runnable task, long timeout, TimeUnit unit) throws Throwable {
/**
* <b>Time:	2018/8/3 14:42</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	裝執(zhí)行線程的中斷策略</b><br/>
*/
class ThrowableTask implements Runnable {
private volatile Throwable e;
@Override
public void run() {
try {
task.run();
} catch (Throwable e) {
//中斷策略: 錄任務(wù)的運行時異常,以便稍后重新拋出該異常合敦,并結(jié)束執(zhí)行線程
this.e = e;
}
}
/**
* <b>Time:	2018/8/3 15:33</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	重新拋出目標任務(wù)運行過程中可能發(fā)生的異常</b><br/>
*
* @throws Throwable
*/
public void rethrow() throws Throwable {
if (null != e)
throw e;
}
}
//將目標任務(wù)運行在明確中斷策略的執(zhí)行線程里
ThrowableTask t = new ThrowableTask();
Thread taskThread = new Thread(t);
taskThread.start();
//啟動中斷任務(wù)
ScheduledFuture<?> future = ses.schedule(() -> {
taskThread.interrupt();
}, timeout, unit);
taskThread.join(unit.toMillis(timeout));
//任務(wù)結(jié)束后初橘,取消掉中斷任務(wù),因為此時目標任務(wù)已經(jīng)結(jié)束充岛,中斷任務(wù)已經(jīng)沒有存在的意義了
future.cancel(true);
//重新拋出任務(wù)執(zhí)行過程中發(fā)生的異常
t.rethrow();
}
}
package cn.weicm.cancel;
import java.util.concurrent.*;
/**
* <b>Time:	2018/8/3 16:38</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	通過Future來取消任務(wù)</b><br/>
* </br>
* <b>優(yōu)點:	</b><br/>
* <ul>
* <li>解決TimedRun2的缺點保檐,可以區(qū)分任務(wù)是如何結(jié)束的</li>
* </ul>
* </br>
* <b>關(guān)于Futrue.cancel:	針對任務(wù)的三種狀態(tài)</b><br/>
* <ul>
* <li>等待狀態(tài):此時不管參數(shù)傳入的是true還是false,任務(wù)都會被標記為取消崔梗,任務(wù)依然保存在隊列中夜只,但當(dāng)輪詢到此任務(wù)時會直接跳過</li>
* <li>運行狀態(tài):此時參數(shù)傳入true會中斷正在執(zhí)行的任務(wù);傳入false則不會中斷任務(wù)蒜魄,而是讓任務(wù)繼續(xù)運行直到結(jié)束</li>
* <li>完成狀態(tài):此時不管參數(shù)傳入的是true還是false扔亥,cancel都不起作用爪膊,因為任務(wù)已經(jīng)完成了</li>
* </ul>
*/
public class TimedRun3 {
private static final ExecutorService es = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws Exception{
try {
timedRun(() -> {
System.out.println("Task start ...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("Task is canceled!");
//假設(shè)任務(wù)不響應(yīng)中斷,即不拋出InterruptedException砸王,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)推盛,而是直接退出
return;
}
System.out.println("Task end ...");
}, 1, TimeUnit.SECONDS);
} finally {
es.shutdown();
}
}
/**
* <b>Time:	2018/8/3 14:20</b><br/>
* <b>Auth:	weicm</b><br/>
* <br/>
* <b>Desp:	在制定時限內(nèi)運行任務(wù),超過時限則取消任務(wù)</b><br/>
*
* @param task 任務(wù)
* @param timeout 時限數(shù)量
* @param unit 時限單位
*/
static void timedRun(Runnable task, long timeout, TimeUnit unit) throws Exception {
Future<?> future = es.submit(task);
try {
future.get(timeout, unit);
} catch (InterruptedException e) {
//當(dāng)前線程被中斷谦铃,恢復(fù)中斷狀態(tài)以傳遞中斷信息
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
//目標任務(wù)執(zhí)行過程發(fā)生運行時異常耘成,直接拋出
throw e;
} catch (TimeoutException e) {
//目標任務(wù)執(zhí)行超時,接下來取消任務(wù)驹闰,因為已經(jīng)不需要結(jié)果了
} finally {
//如果任務(wù)已經(jīng)運行完瘪菌,則取消操作不產(chǎn)生任何效果;如果任務(wù)由與異常而終止嘹朗,不管什么異常师妙,則取消任務(wù),因為已經(jīng)不需要結(jié)果了
//取消那些不在需要結(jié)果的任務(wù)是一種良好的習(xí)慣屹培!
future.cancel(true);
}
}
}
不可中斷的阻塞
- Thread.interrupt對于執(zhí)行不可中斷的操作而阻塞的線程默穴,只能設(shè)置線程的中斷狀態(tài),除此之外沒有其他任何作用褪秀;但可以使用類似于中斷的手段來停止這些線程蓄诽,但是必須知道線程阻塞的原因。
常見不可中斷的阻塞操作
- Java.io包中的同步Socket I/O: InputStream.read和OutputStream.write媒吗,可以關(guān)閉套接字(Socket.close)使他們拋出SocketException仑氛。
- Java.io包中的同步I/O: 當(dāng)中斷一個正在InterruptibleChannel上等待的線程時,將拋出ClosedByInterruptException并關(guān)閉鏈路闸英,這還會使得其他在該鏈路上阻塞的線程同樣拋出ClosedByInterruptException锯岖。當(dāng)關(guān)閉一個InterruptibleChannel時,將導(dǎo)致所有在鏈路操作上阻塞的線程都拋出AsynchronousCloseException甫何。
- Selector的異步I/O: 如果一個線程在調(diào)用Selector.select方法時阻塞了出吹,那么調(diào)用close或wakeup方法會使線程拋出ClosedSelectorException并提前返回。
- 獲取某個鎖: 如果一個線程由與等待某個內(nèi)置鎖而阻塞沛豌,那么將無法響應(yīng)中斷趋箩,因為線程認為它肯定會獲得鎖,所以將不會理會中斷請求加派。但是Lock類中提供了lockInterruptiblely方法,該方法允許在等待一個鎖的同時仍能響應(yīng)中斷跳芳。