線程池執(zhí)行的用戶任務(wù)拋出異常會怎樣?
ThreadPoolExecutor.execute
源碼分析
看源碼可以知道,ThreadPoolExecutor中的任務(wù)都是在runWorker中執(zhí)行的

通過源碼可以看到
1149行執(zhí)行用戶任務(wù) 1150~1155處理捕獲任務(wù)異常,并拋出 拋出異常后會退出,從任務(wù)隊列中拉取任務(wù)的循環(huán) 然后執(zhí)行1167行,worker線程退出的邏輯
看一下線程退出的邏輯

如果是異常退出,參數(shù) completedAbruptly為true如果狀態(tài)值比STOP小,即線程池沒有停止,會重新創(chuàng)建一個worker線程
總結(jié)
ThreadPoolExecutor.execute 如果用戶任務(wù)拋出了異常,在線程池運行的狀態(tài)下,會重新創(chuàng)建一個worker線程。
這里就可能存在一個風(fēng)險,即如果用戶任務(wù)大量的拋出異常,可能會導(dǎo)出線程資源頻繁的銷毀、創(chuàng)建。
因此,需要用戶任務(wù)應(yīng)當主動對異常進行處理,而不是消極的拋給線程池。
ThreadPoolExecutor.submit
源碼分析
通過 ThreadPoolExecutor.submit 提交的用戶任務(wù),會包裝成一個FutureTask,返回一個Future對象。因此異常處理的邏輯和ThreadPoolExecutor.execute有些差別
看一個FutureTask.run方法

從源碼可以看到,F(xiàn)utureTask執(zhí)行用戶任務(wù),如果異常,不會對外拋出,僅是記錄
但是在調(diào)用Future.get時,會拋出異常,但此時的線程不是線程池的線程了,而是用戶線程,因此對線程池是友好的。
總結(jié)
ThreadPoolExecutor.submit 提交的用戶任務(wù),會包裝成一個FutureTask,F(xiàn)utureTask執(zhí)行用戶任務(wù),如果異常,不會對外拋出,僅是記錄,但是在調(diào)用Future.get時,會拋出異常。
異常是在用戶線程中拋出的,因此不影響線程池中的線程。
ScheduledThreadPoolExecutor.schedule
源碼分析
ScheduledThreadPoolExecutor.schedule會將用戶任務(wù)包裝為ScheduledFutureTask,ScheduledFutureTask是FutureTask的子類,看下ScheduledFutureTask的執(zhí)行邏輯

ScheduledFutureTask是FutureTask的子類,所以有異常時,也不是拋出,而是記錄
293行對于非周期性任務(wù),執(zhí)行一次,如果有異常,不拋出,僅記錄 294~296對于周期性任務(wù),執(zhí)行完本次后,會設(shè)置下次執(zhí)行的時間。如果本次出現(xiàn)異常, ScheduledFutureTask.super.runAndReset()返回結(jié)果為false(這個可以從源碼看到),此時不會設(shè)置下次任務(wù)調(diào)度的時間了,因此會導(dǎo)致周期性任務(wù)失效的現(xiàn)象,并且異常信息不會拋出,也不會打印
總結(jié)
ScheduledThreadPoolExecutor.schedule提交的用戶任務(wù),如果出現(xiàn)異常,是不會拋出的,也不會打印。
對于非周期性任務(wù)和周期性任務(wù),執(zhí)行異常,都沒有辦法感知(不拋出、看不到)。
對于周期性任務(wù),任何一次調(diào)度時,任務(wù)出現(xiàn)異常,都會導(dǎo)致后續(xù)無法調(diào)度。
因此,在使用這個線程池時,我們應(yīng)當保證用戶任務(wù)不會拋出異常到執(zhí)行線程,避免任務(wù)調(diào)度失效,和異常排查困難等問題。
思考:ThreadPoolExecutor.execute發(fā)生異常時為什么要退出
ThreadPoolExecutor.execute出現(xiàn)用戶任務(wù)異常時,為什么要退出當前線程,再重新創(chuàng)建一個線程呢?
我思考了半天,覺得原因之一可能是:
ThreadPoolExecutor的可以指定線程工廠,如果我的線程工廠是這樣的
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 處理異常的邏輯
}
});
return thread;
}
};
即在ThreadFactory創(chuàng)建線程時,指定了對未捕獲的異常的處理器。
這種情況下,如果線程池不把發(fā)生異常的線程退出,可能會導(dǎo)致異常沒有走到用戶期望的邏輯上,因此需要將發(fā)生異常的線程退出,然后JVM調(diào)用UncaughtExceptionHandler。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取
