線程池中的一個線程異常了會被怎麼處理?
估計很多人會是以下三點答案(me too):
1.拋異常出來並打印在控制檯上
2.其他線程任務不受影響
3.異常線程會被回收
但是這裏我先提前說一下以上三點不全對,下面我們來具體分析一下。
話不多說用代碼來證明
熟悉Executors線程池(本文線程池都是指Executors)都知道 有兩種提交線程的方式execute和submit方式,下面將以這兩種提交方式來驗證。
貼個代碼湊個數
public static void main(String[] args) {
ThreadPoolTaskExecutor executorService = buildThreadPoolTaskExecutor();
executorService.execute(() -> run("execute方法"));
executorService.submit(() -> run("submit方法"));
}
private static void run(String name) {
String printStr = "【thread-name:" + Thread.currentThread().getName() + ",執行方式:" + name+"】";
System.out.println(printStr);
throw new RuntimeException(printStr + ",出現異常");
}
private static ThreadPoolTaskExecutor buildThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setThreadNamePrefix("(小羅技術筆記)-");
executorService.setCorePoolSize(5);
executorService.setMaxPoolSize(10);
executorService.setQueueCapacity(100);
executorService.setKeepAliveSeconds(10);
executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executorService.initialize();
return executorService;
}
觀看執行結果,誒好奇怪
execute執行方式拋出異常顯示在控制檯了。
submit執行方式啥都沒有輸出。
衆所周知submit底層其實也是調用的execute,因此它也有異常只是處理方法不一樣,它們的區別是:
1、execute沒有返回值。可以執行任務,但無法判斷任務是否成功完成。——實現Runnable接口
2、submit返回一個future。可以用這個future來判斷任務是否成功完成。——實現Callable接口
那怎麼拿到submit中的異常呢?還是用代碼來說話
Future<?> result=executorService.submit(() -> run("submit方法"));
try {
result.get();
}catch (Exception e){
e.printStackTrace();
}
獲取了一下submit方法的返回結果,發現有異常了和execute一樣了,所以第一點拋異常出來並打印在控制檯上不是全對的!
到這估計大家和我一樣都有一個疑問了 ,爲啥execute直接拋出異常,submit沒有呢?
要知道這個答案就只能去翻源碼看了
在java.util.concurrent.ThreadPoolExecutor#runWorker中拋出了運行異常:
在java.lang.ThreadGroup#uncaughtException進行了異常處理:
uncaughtException是什麼,我也不知道,百度了一下說這個方法是JVM調用的,在線程中只需要指定我們想要的處理方式即可
說道這裏你可能會吐槽說了這麼多submit到底爲啥沒有直接拋出異常,到底是怎麼處理了,不要慌我們再看源碼找答案
看submit源碼會發現,submit中傳進來的task會被封裝成一個FutureTask,然後再調用execute,最後返回FutureTask。
你會發現走的是execute方法,如下圖,會發現此時的task已經是FutureTask,所以再去看一下FutureTask的run方法是咋寫的。
異常被存起來了…,再看源碼是怎麼實現的
返回參數裏面有一個狀態state翻源碼發現在report方法中同時用到outcom和state狀態判斷打個斷點發現還真是,當state爲3時拋出了異常。
同理在找尋一下report調用位置可以很明顯發現是FutureTask中get方法調用了,結合上面可以很明確了submit提交時異常被存儲在線程結果信息中,當調用get方法是判斷線程運行結果狀態,有異常就拋出存儲的異常信息,因此submit運行異常我們只能用get方法來拿到。
至於第二點我就不多說了,平時使用中就已經證明了!
第三點線程出異常了不是被線程池回收嘛?
看源碼我們知道線程運行最後總有一個processWorkerExit要執行,看看裏面的實現
很神奇先刪掉線程又再調用創建線程的方法,所以異常線程不是被回收,而是被刪除了再創建一個新的頂替了。
到此線程池中的線程異常了會被怎麼處理講完了,總結一下就是:
1、execute方法,可以看異常輸出在控制檯,而submit在控制檯沒有直接輸出,必須調用Future.get()方法時,可以捕獲到異常。
2、一個線程出現異常不會影響線程池裏面其他線程的正常執行。
3、線程不是被回收而是線程池把這個線程移除掉,同時創建一個新的線程放到線程池中。
4、還有源碼是個好東西,答案都在裏面,就是太難看懂了