线程池异常日志处理

线程池是作为池化技术的一种常见应用,今天来聊一聊线程池的异常日志处理策略!

先看一个栗子,先定义一个线程池实例


    @Bean
    public ThreadPoolExecutor uncaughtExceptionExecutor() {

        ThreadFactory threadFactory = new ThreadFactoryBuilder().
                setNameFormat("uncaughtException-worker-%d").build();
        int processors = Runtime.getRuntime().availableProcessors();
        log.info("processors:{}", processors);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(processors,
                processors * 2,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(1000),
                threadFactory,
                new ThreadPoolExecutor.AbortPolicy());
        return executor;
    }

测试案例


    @Override
    public void executorLogTest() throws Exception {
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            uncaughtExceptionExecutor.execute(() -> {
                String str = null;
                log.info("execute str:{}", str);

                int length = str.length();

                countDownLatch.countDown();

            });
        }

        countDownLatch.await();

        log.info("concurrent execute finished!");
    }

意图很明显,第十行会报空指针,希望会在日志文件中将堆栈信息的日志打印出来,但真实的情况会是什么样的呢?

2022-04-30 11:54:26.593 [uncaughtException-worker-5] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.593 [uncaughtException-worker-3] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.593 [uncaughtException-worker-2] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.593 [uncaughtException-worker-0] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.593 [uncaughtException-worker-4] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.593 [uncaughtException-worker-1] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.599 [uncaughtException-worker-10] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.598 [uncaughtException-worker-8] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.599 [uncaughtException-worker-6] INFO  c.t.m.s.i.UserServiceImpl - execute str:null
2022-04-30 11:54:26.599 [uncaughtException-worker-9] INFO  c.t.m.s.i.UserServiceImpl - execute str:null

怎么log文件中没有异常的堆栈日志呢?通过分析源码ThreadPoolExecutor 会发现这个问题的根源!

接着看afterExecute()方法

此时答案已经浮出水面了,线程池为了不影响其他线程的执行,会将当前线程的异常全部捕获,但是这样会带来一个问题,若开发人员忘记了处理异常,就会莫名缺少一些关键的堆栈日志,处理这个问题主要有三种方案。

  • 自行处理异常

最简单直接的方法就是自己动手丰衣足食,将线程池中的业务逻辑catch起来然后自己处理,这个方案会带来代码上的冗余,尤其是业务代码没有checkException,另外有时开发人员也会忘记添加catch,显然,此方案不是最优方案;

    @Override
    public void executorLogTest() throws Exception {
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            uncaughtExceptionExecutor.execute(() -> {
                try {
                    String str = null;
                    log.info("execute str:{}", str);

                    int length = str.length();

                } catch (Exception e) {
                    log.error("executorLogTest occur error", e);

                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        countDownLatch.await();

        log.info("concurrent execute finished!");
    }

按照这种方式处理后,log文件中会显示堆栈信息

  • 继承ThreadPoolExecutor并重写afterExecute方法

package com.tml.mouseDemo.model;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

    public CustomThreadPoolExecutor(int corePoolSize,
                                    int maximumPoolSize,
                                    long keepAliveTime,
                                    TimeUnit unit,
                                    BlockingQueue<Runnable> workQueue,
                                    ThreadFactory threadFactory,
                                    RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        log.error("CustomThreadPoolExecutor occur error", t);
    }
}

测试用例


@Override
    public void customExecutor() throws InterruptedException {
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            customExecutor.execute(() -> {
                String str = null;
                log.info("customExecutor str:{}", str);

                int length = str.length();

                countDownLatch.countDown();

            });

        }

        countDownLatch.await();

        log.info("concurrent execute finished!");
    }

执行日志结果

  • 实现Thread.UncaughtExceptionHandler接口

   Thread.UncaughtExceptionHandler exceptionHandler = (Thread t, Throwable e) -> {
        log.info("current thread occurs error!", e);
    };

    @Bean
    public ThreadPoolExecutor executor() {

        ThreadFactory threadFactory = new ThreadFactoryBuilder().
                setUncaughtExceptionHandler(exceptionHandler).
                setNameFormat("mouse-worker-%d").build();
        int processors = Runtime.getRuntime().availableProcessors();
        log.info("processors:{}", processors);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(processors,
                processors * 2,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(1000),
                threadFactory,
                new ThreadPoolExecutor.AbortPolicy());
        

测试用例


    @Override
    public void executorLogTest() throws Exception {
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            executor.execute(() -> {
                String str = null;
                log.info("execute str:{}", str);

                int length = str.length();
            });
        }

        countDownLatch.await();

        log.info("concurrent execute finished!");
    }

执行结果

实际应用中,第二种和第三种方案,可以根据自己的喜好自行选择。但若你在程序中使用了CountDownLatch,那么官方的建议就是需要try-catch-finally三连的。

猜你喜欢

转载自blog.csdn.net/tianmlin1/article/details/124520256