必须在任务内部用try-catch显式捕获异常,否则线程会终止、异常丢失、资源泄漏;execute需自行包裹,submit需调用get()提取cause;afterExecute和UncaughtExceptionHandler仅作全局兜底监控。

必须在任务内部用 try-catch 显式捕获异常,这是最直接、最可靠的方式。线程池本身不会替你兜底,一旦 Runnable 的 run() 或 Callable 的 call() 抛出未捕获异常,工作线程就会终止——即使线程池后续补新线程,原始异常也大概率丢失,排查困难,还可能引发资源泄漏。
所有 execute 提交的 Runnable 必须自己包 try-catch
execute 是纯异步提交,不返回 Future,异常无法被外部感知。若不主动捕获,异常会击穿执行栈,触发 Thread.dispatchUncaughtException(),线程直接退出。
- 错误写法:
executor.execute(() -> riskyOperation());—— 异常一出,线程挂掉 - 正确写法:
executor.execute(() -> { try { riskyOperation(); } catch (Exception e) { log.error("task failed", e); } }); - 推荐封装:
safeRun(Runnable)工具方法,统一日志 + 避免重复模板代码
submit 提交的 Callable 要主动调用 get() 暴露异常
submit 会把异常封装进 Future,但它是“静默存储”:你不调 future.get(),异常就永远不抛出,也不会记录,等于白扔。
- 必须配合
get()使用,并捕获ExecutionException,从中提取真实原因:ex.getCause() - fire-and-forget 场景(比如发通知、打点)仍需在 call() 内部 try-catch,不能依赖 get()
- 避免只 submit 不 get,尤其在无结果反馈的后台任务中
用 afterExecute 做漏网之鱼的全局监控
继承 ThreadPoolExecutor,重写 afterExecute(Runnable, Throwable),当第二个参数非 null,说明有任务没做内部捕获。
立即学习“Java免费学习笔记(深入)”;
- 适合统一上报错误指标(如 Prometheus counter)、触发告警、记录结构化日志
- 注意:它只对
execute场景生效,submit的异常不会传进来 - 不是替代方案,而是最后一道“发现”防线,不能代替任务内捕获
为线程工厂设置 UncaughtExceptionHandler 作兜底
这是针对完全裸奔任务(连 try-catch 都没加)的最后一道屏障,尤其适合老项目或第三方代码接入场景。
- 在
ThreadFactory中为每个线程设置 handler,确保异常至少能落盘或上报 - handler 内容要轻量:只记录完整堆栈到 ELK/Loki,不要 sleep、远程调用或锁操作
- 别指望它恢复线程或重试——它的作用是“不让异常消失”,不是“让任务继续”


















