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中是没法捕获到异常的。

因为本人水平有限,文章中若是有不严谨的地方还请提出来,愿闻其详。

相关文章
相关标签/搜索