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

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

估計不少人會是如下三點答案(me too):ui

1.拋異常出來並打印在控制檯上線程

2.其餘線程任務不受影響code

3.異常線程會被回收接口

可是這裏我先提早說一下以上三點不全對,下面咱們來具體分析一下。ci

#話很少說用代碼來證實get

熟悉Executors線程池(本文線程池都是指Executors)都知道 有兩種提交線程的方式execute和submit方式,下面將以這兩種提交方式來驗證。源碼

貼個代碼湊個數it

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

觀看執行結果,誒好奇怪io

execute執行方式拋出異常顯示在控制檯了。

submit執行方式啥都沒有輸出。

衆所周知submit底層其實也是調用的execute,所以它也有異常只是處理方法不同,它們的區別是:

一、execute沒有返回值。能夠執行任務,但沒法判斷任務是否成功完成。——實現Runnable接口

二、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

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

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

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

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

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

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

xuanchuantu.png

相關文章
相關標籤/搜索