Java线程池run方法异常被吞问题排查

诡异现象

线程打印了方法入口日志后续再无日志产生,也无相关异常日志。

排查结果

线程池业务代码写法有问题。在当线程代码执行发生异常的情况下,异常被线程池源码内部捕获未打印 。

代码分析

简化后的代码如下:

// 调用代码
try {
    // 线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 20, TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(20),
        new SimpleThreadFactory());
    threadPoolExecutor.execute(new ProcessRun());
} catch (Exception e) {
    // 此处异常打印无效
    e.printStackTrace();
}


/**
 * Runnable类
 */
public class ProcessRun implements Runnable {
    public void run() {
        // 模拟异常
        int a = 1 / 0;
    }
}

/**
 * threadFactory
 */
private  class SimpleThreadFactory implements ThreadFactory {

    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }

}

当ProcessRun执行异常时, 可以看一下线程池源码。

线程执行实际执行的是ThreadPoolExecutor.Worker类的run方法–> runWorker() 。

try {
    // 前置钩子函数
    beforeExecute(wt, task);
    Throwable thrown = null;
    try {
        // 执行
        task.run();
    } catch (RuntimeException x) {
        // 异常向上抛出
        thrown = x; throw x;
    } catch (Error x) {
        thrown = x; throw x;
    } catch (Throwable x) {
        thrown = x; throw new Error(x);
    } finally {
        // 后置钩子函数
        afterExecute(task, thrown);
    }
} finally {
    task = null;
    w.completedTasks++;
    w.unlock();
}

可以看到当执行发生异常时,catch是会往外抛的,而在finally里面线程池留了个钩子函数afterExecute(),这里我们可以重写该方法来实现异常的捕获。而这个异常实际是被Thread#dispatchUncaughtException捕获了, 我们可以看到注释 将未捕获的异常分派给处理程序。此方法仅由JVM调用。在这个方法里回去获取uncaughtExceptionHandler去处理异常。

/**
 * 将未捕获的异常分派给处理程序。此方法仅由JVM调用。
 * Dispatch an uncaught exception to the handler. This method is
 * intended to be called only by the JVM.
 */
private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}

当我们没有指定uncaughtExceptionHandler时, 它是由ThreadGroup#uncaughtException进行处理的。 可以看到代码仅仅是在控制台打印了异常,所以日志系统的异常被吞了。

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            // 控制台打印异常
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

所以我们想要捕获异常,也可以通过自定义UncaughtExceptionHandler

总结

代码写法有问题, 要想异常不被吞可以使用下面三种方式。 最简单常用的还是第一种。

  1. Processs#run方法内部进行try…catch

  2. 自定义UncaughtExceptionHandler

    thread.setUncaughtExceptionHandler(new CustomExceptionHandler());

  3. 重写 ThreadPoolExecutor#afterExecute

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值