诡异现象
线程打印了方法入口日志后续再无日志产生,也无相关异常日志。
排查结果
线程池业务代码写法有问题。在当线程代码执行发生异常的情况下,异常被线程池源码内部捕获未打印 。
代码分析
简化后的代码如下:
// 调用代码
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。
总结
代码写法有问题, 要想异常不被吞可以使用下面三种方式。 最简单常用的还是第一种。
-
Processs#run方法内部进行try…catch
-
自定义UncaughtExceptionHandler
thread.setUncaughtExceptionHandler(new CustomExceptionHandler());
-
重写 ThreadPoolExecutor#afterExecute