ThreadPoolExcutor 線程池 異常處理 (下篇)

前言

由於這是以前面試的一個題目,因此印象比較深入,前幾天寫了一篇文章:ThreadPoolExcutor 線程池 異常處理 (上篇) 中已經介紹了線程池異常的一些問題以及一步步分析了裏面的一些源代碼,今天就來繼續說下如何防範這種狀況。html

結論

這裏直接拋出結論,而後再一個個分析:java

  • 在咱們提供的Runnable的run方法中捕獲任務代碼可能拋出的全部異常,包括未檢測異常
  • 使用ExecutorService.submit執行任務,利用返回的Future對象的get方法接收拋出的異常,而後進行處理
  • 重寫ThreadPoolExecutor.afterExecute方法,處理傳遞到afterExecute方法中的異常
  • 爲工做者線程設置UncaughtExceptionHandler,在uncaughtException方法中處理異常 (不推薦)

分析解讀

Runnable的run方法中捕獲任務代碼可能拋出的全部異常

這個其實最簡單,可是每每面試官問這個問題 考察的點也不在這裏。具體的方式能夠參考我以前的一篇文章:論如何優雅的自定義ThreadPoolExecutor線程池面試

核心代碼以下:
異步

使用ExecutorService.submit執行任務,利用返回的Future對象的get方法接收拋出的異常

1, 使用submit執行異步任務,而後經過Future的get方法來接收異常。演示以下:
衝圖片能夠看到,使用了get方法後,這裏直接接收到了異常信息。
ide

2, 這裏newTaskFor返回的是FutureTask,而後傳遞給了execute方法:
測試

3, 接着咱們繼續往下跟蹤execute方法,發現這裏調用的是ThreadExecutor中的execute方法,在ThreadPoolExcutor 線程池 異常處理 (上篇) 咱們已經分析過這裏,最終會到addWorker方法中執行線程的start()方法,由於咱們在上一張圖片傳遞的是FutureTask, 因此咱們繼續跟蹤FutureTask中的run方法:
ui

4, 到了FutureTask.run() 方法中,一切彷佛都已經明瞭,這裏會有catch捕獲當前線程拋出的異常,緊接着咱們看看setException作了什麼事情:
spa

5,setExcetion首先是將一個異常信息賦值給一個全局變量outcome,而且將全局的任務狀態state字段經過CAS更新爲3(異常狀態)
而後最後作一些清理工做。線程

6,finishCompletion後續是作一些線程池的清理工做,這裏涉及到線程池以及線程池中的等待隊列的操做,不清楚的同窗能夠看下線程池實現代碼。到了這裏線程池中的線程執行已經完畢了,下面再去跟蹤一下FutureTask.get()方法。
3d

7,這裏是FutureTask.get()的底層實現,這裏其實會拿上面的setException方法中設置的outcome和state作一些邏輯判斷,到了這裏就直接往上拋出了異常,因此咱們在最開始的main方法中才可以捕獲到這個異常。

重寫ThreadPoolExecutor.afterExecute方法,處理傳遞到afterExecute方法中的異常

這裏爲什麼要重寫afterExecute方法呢?由於線程執行完畢後必定會執行此方法,源碼以下:

因此咱們能夠重寫此方法來達到接收異常的目的。

爲工做者線程設置UncaughtExceptionHandler,在uncaughtException方法中處理異常 (不推薦)

1,咱們在以前ThreadExecutor->Worker->run方法中直接往上拋出了異常,可是這些異常拋到哪裏了呢?

2,經過查詢JVM的一些資料,最終的異常會到Thread.dispatchUncaughtException中,以下圖:

3,因此當咱們自定義UncaughtExceptionHandler時就能夠捕獲到

具體測試代碼以下:

/**
 * 測試singleThreadPool異常問題
 *
 * @author: wangmeng
 * @date: 2019/3/25 23:40
 */
public class ThreadPoolException {
    private final static Logger LOGGER = LoggerFactory.getLogger(ThreadPoolException.class);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService execute = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(new MyHandler()).build());

        execute.execute(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("=====11=======");
            }
        });

        TimeUnit.SECONDS.sleep(5);
        execute.execute(new Run1());
    }


    private static class Run1 implements Runnable {
        @Override
        public void run() {
            int count = 0;
            while (true) {
                count++;
                LOGGER.info("-------222-------------{}", count);

                if (count == 10) {
                    System.out.println(1 / 0);
                    try {
                    } catch (Exception e) {
                        LOGGER.error("Exception",e);
                    }
                }

                if (count == 20) {
                    LOGGER.info("count={}", count);
                    break;
                }
            }
        }
    }
}

class MyHandler implements Thread.UncaughtExceptionHandler {
    private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage());
    }
}

上面說了實際上是不推薦重寫UncaughtExceptionHandler 的,由於UncaughtExceptionHandler 只有在execute.execute()方法中才生效,在execute.submit中是沒法捕獲到異常的。

因爲本人水平有限,文章中若是有不嚴謹的地方還請提出來,願聞其詳。

相關文章
相關標籤/搜索