并发异常捕获本质是异常可见性问题:子线程异常不会自动传播至主线程,需在子线程内try-catch、future调用get()解包、completablefuture用exceptionally/handle/whencomplete分流处理,或通过定制threadfactory设置线程级uncaughtexceptionhandler。

并发环境中的异常捕获顺序问题,本质不是“多个catch谁先匹配”的语法顺序问题,而是**异常发生位置与主线程可见性之间的错位问题**。Java的try-catch语法本身只作用于当前线程栈,而并发任务(如Thread、Future、CompletableFuture)中抛出的异常默认不会自动传播到发起线程,因此不存在传统意义上的“捕获顺序”冲突,但容易误以为“没捕获到”——其实是根本没传出来。
子线程异常必须显式捕获或委托处理
普通Thread中未用try-catch包裹的代码,一旦抛异常,会直接终止该线程,且异常不会向上冒泡。此时主线程完全无感知。
- 推荐做法:在run()或lambda内部主动包一层try-catch,按业务逻辑分类处理
- 兜底做法:为线程设置UncaughtExceptionHandler,捕获所有漏网异常
- 错误示范:只在主线程写一个try-catch,期望它能捕获子线程里的NullPointerException——这不会生效
Future任务的异常需在get()时解包
ExecutorService.submit(Callable)返回的Future,其异常被延迟封装为ExecutionException。调用get()才是异常真正“浮现”的时刻。
- 必须对get()调用做try-catch,否则主线程可能因未处理ExecutionException而中断
- 原始异常藏在e.getCause()里,直接打印e.getMessage()只能看到“ExecutionException”,要展开看根因
- 若不调用get()(比如只submit后就shutdown),异常将彻底丢失,日志里也不会体现
CompletableFuture提供声明式异常分流
它支持在异步链路上按类型或条件分离异常处理逻辑,避免把所有异常都挤到同一个catch里。
- exceptionally(Function
):统一处理所有异常,返回替代值 - handle(BiFunction
):无论成功或失败都执行,可判断e是否为null来分支处理 - whenComplete(BiConsumer
):仅做副作用(如打日志、发告警),不改变结果 - 注意:这些方法本身不改变异常传播路径,只是让处理更清晰,仍需确保链路末端有消费
线程池全局异常处理器要慎用
通过Thread.setDefaultUncaughtExceptionHandler设置的处理器,会影响所有未显式设置handler的线程,包括线程池内部工作线程。
- 优点:兜底可靠,防止异常静默丢失
- 风险:可能掩盖本该由业务层处理的特定异常(如重试场景下的TransientException)
- 建议:在线程池创建时,用自定义ThreadFactory为每个Worker线程单独设置handler,与业务上下文绑定
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











