如何实现线程池中异常捕获

257 阅读2分钟

线程出现异常

线程执行任务时,没有添加异常处理。导致任务内部发生异常时,内部错误无法被记录下来。

Thread thread =new Thread(()->{
    if (1 == 1) {
        log.error("error");
        throw new RuntimeException("异常了");
    }
});
thread.start();
}

可以看到,异常不会打印日志,而是输出在控制台上了。那么异常去了哪里?

异常去了哪里?

正常来说,如果我们进行了异常捕获,是可以看到日志信息的。

Thread thread = new Thread(()->{
    try{
        log.info("hello");
        throw new RuntimeException("运行时异常了");
    }catch (Exception e){
        log.error("异常发生",e);
    }
});
thread.start();

但是如果是一个未捕获的异常,异常是抛到控制台上的,什么原因呢?

如果一个异常未被捕获,从线程中抛了出来。JVM会回调T hread 类中dispatchUncaughtException方法。

在这方法中,就是获取一个默认的异常的处理器,我们来看看这个处理器它是怎么做的?

可以看到这里,他不会记录日志信息,而是将异常抛到了控制台中,那么线上我们一般是通过日志来排查问题,所以,我们怎么将控制台的输出转变成日志输出呢?

解决方案

其实,很简单,就是给线程添加一个异常捕获处理器,以后抛了异常,就给它转成日志。这样才能及时发现问题。

首先定义一个异常处理器:

/**
 * 自定义异常处理器(将异常时控制台输出,装变成日志输出)
 */
@Slf4j
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        log.error("Exception Thread",e);
    }
}

设置异常处理器

Thread thread =new Thread(()->{
    if (1 == 1) {
        log.error("error");
        throw new RuntimeException("异常了");
    }
});
// 设置异常处理器
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
thread.start();

可以看到即使没有获取异常,抛出的异常,我们也记录在日志中。

线程池如何进行异常处理呢?

可以通过线程池的ThreadFactory,创建线程的工厂,创建线程的时候给线程添加异常捕获。

首先自定义线程工厂,然后就是在保持原有线程工厂的能力,然后额外的配置我们自己自定义的异常处理器

/**
 * 自定义线程工厂(提供处理异常的能力)
 */
public class MyThreadFactory implements ThreadFactory {

    private static final MyUncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = new MyUncaughtExceptionHandler();

    private ThreadFactory originalThreadFactory;

    public MyThreadFactory(ThreadFactory originalThreadFactory) {
        this.originalThreadFactory = originalThreadFactory;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = originalThreadFactory.newThread(r);
        // 设置我们自定义的异常处理器
        thread.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER);
        return thread;
    }
}

在线程池中配置我们的线程池工厂:

这里我使用的是Spring提供的线程池。

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.setThreadFactory(new MyThreadFactory(executor));
        executor.initialize();
        return executor;
    }
}

new MyThreadFactory() 这里为什么可以传入这个线程池类,因为Spring中这个类本身就是一个线程工厂的类。

可以从下面看到 ThreadPoolTaskExecutor 继承了 ExecutorConfigurationSupport

可以从关系图中看到是实现了 ThreadFactory 接口的