70%人答不全!線程池中的一個線程異常了會被怎麼處理?

線程池中的一個線程異常了會被怎麼處理?

估計很多人會是以下三點答案(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;
}

20200425001.png

觀看執行結果,誒好奇怪

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();
}

20200425002.jpg

獲取了一下submit方法的返回結果,發現有異常了和execute一樣了,所以第一點拋異常出來並打印在控制檯上不是全對的!

到這估計大家和我一樣都有一個疑問了 ,爲啥execute直接拋出異常,submit沒有呢?

要知道這個答案就只能去翻源碼看了

在java.util.concurrent.ThreadPoolExecutor#runWorker中拋出了運行異常:
20200425003.png

在java.lang.ThreadGroup#uncaughtException進行了異常處理:
20200425004.png

uncaughtException是什麼,我也不知道,百度了一下說這個方法是JVM調用的,在線程中只需要指定我們想要的處理方式即可
20200425005.png

說道這裏你可能會吐槽說了這麼多submit到底爲啥沒有直接拋出異常,到底是怎麼處理了,不要慌我們再看源碼找答案
20200425006.png

看submit源碼會發現,submit中傳進來的task會被封裝成一個FutureTask,然後再調用execute,最後返回FutureTask。
20200425007.png

你會發現走的是execute方法,如下圖,會發現此時的task已經是FutureTask,所以再去看一下FutureTask的run方法是咋寫的。
20200425008.png

異常被存起來了…,再看源碼是怎麼實現的
20200425009.png

返回參數裏面有一個狀態state翻源碼發現在report方法中同時用到outcom和state狀態判斷打個斷點發現還真是,當state爲3時拋出了異常。
20200425010.png

同理在找尋一下report調用位置可以很明顯發現是FutureTask中get方法調用了,結合上面可以很明確了submit提交時異常被存儲在線程結果信息中,當調用get方法是判斷線程運行結果狀態,有異常就拋出存儲的異常信息,因此submit運行異常我們只能用get方法來拿到。

至於第二點我就不多說了,平時使用中就已經證明了!

第三點線程出異常了不是被線程池回收嘛?

看源碼我們知道線程運行最後總有一個processWorkerExit要執行,看看裏面的實現

20200425011.png

很神奇先刪掉線程又再調用創建線程的方法,所以異常線程不是被回收,而是被刪除了再創建一個新的頂替了。

到此線程池中的線程異常了會被怎麼處理講完了,總結一下就是:

1、execute方法,可以看異常輸出在控制檯,而submit在控制檯沒有直接輸出,必須調用Future.get()方法時,可以捕獲到異常。

2、一個線程出現異常不會影響線程池裏面其他線程的正常執行。

3、線程不是被回收而是線程池把這個線程移除掉,同時創建一個新的線程放到線程池中。

4、還有源碼是個好東西,答案都在裏面,就是太難看懂了

xuanchuantu.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章