关键是在threadfactory的newthread()中为每个线程设置uncaughtexceptionhandler,确保线程池复用线程时异常可捕获、可记录、可告警;同时submit任务需主动调用future.get()处理executionexception。

在线程池中捕获未处理的运行时异常,关键不是“在线程工厂里捕获”,而是通过自定义 ThreadFactory 为每个新创建的线程**预先设置好 UncaughtExceptionHandler**。线程工厂本身不执行任务、也不运行代码,它只负责造线程;异常发生在任务执行过程中,而处理器会在异常真正“无人接手”时被自动调用。
为什么必须在 ThreadFactory 中设置 UncaughtExceptionHandler
线程池(如 ThreadPoolExecutor)内部复用线程,不会为每次任务重新创建线程。默认线程没有绑定异常处理器,一旦任务中抛出未 catch 的 RuntimeException,线程就会静默终止——控制台可能一闪而过堆栈,线上环境几乎零可见性,任务丢失且无告警。
只有在 newThread() 时主动调用 t.setUncaughtExceptionHandler(...),才能确保每个工作线程从诞生起就具备兜底能力。
标准写法:在 newThread 中绑定处理器
下面是一个生产可用的实现示例,包含线程命名、优先级、守护状态和异常处理四要素:
- 使用带计数器的前缀名(如
"biz-order-01"),便于日志追踪和jstack定位 - 显式设置
setDaemon(false),避免业务线程被 JVM 强制终止 - 设置合理优先级(通常无需改,默认
NORM_PRIORITY即可) - 在 handler 中记录线程名、时间戳、traceId(如有)、完整异常链(
e.getCause()不可省)
示例代码:
public class NamedThreadFactory implements ThreadFactory {
private final String prefix;
private final AtomicInteger counter = new AtomicInteger(1);
private final boolean daemon;
public NamedThreadFactory(String prefix, boolean daemon) {
this.prefix = prefix;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, prefix + "-" + counter.getAndIncrement());
t.setDaemon(daemon);
t.setPriority(Thread.NORM_PRIORITY);
t.setUncaughtExceptionHandler((thread, ex) -> {
// 记录到业务日志系统(非 System.err)
log.error("【线程异常终止】线程名={}, 时间={}, traceId={}, 原因={}",
thread.getName(),
LocalDateTime.now(),
MDC.get("traceId"),
ex.getMessage(),
ex); // 全量堆栈
// 可选:触发告警、上报监控平台
});
return t;
}
}
搭配线程池使用的正确姿势
不能只写 ThreadFactory,还要确保线程池构造时实际使用它:
- 用
new ThreadPoolExecutor(..., new NamedThreadFactory("order-consumer", false)) - 避免混用
Executors.xxx()工厂方法——它们返回的线程池内部使用的是私有无名线程工厂,无法注入 handler - Spring 环境下,若用
@EnableAsync,需配置AsyncConfigurer并重写getAsyncExecutor()返回自定义线程池
补充:别漏掉 Future.get() 的异常
UncaughtExceptionHandler 只捕获 run() 内部未 catch 的异常,对 submit() 提交的 Callable 任务无效——它的异常会包装成 ExecutionException,由 future.get() 抛出。
所以建议:
- 任务逻辑复杂、需强异常反馈时,统一用
Callable<void></void>替代Runnable - 调用
future.get(30, TimeUnit.SECONDS),并处理ExecutionException和TimeoutException - 批量提交用
invokeAll(),遍历每个Future调用get(),防止遗漏
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











