finally块抛异常是异常覆盖问题而非并发问题,会掩盖try中的原始异常;真正需防范的是finally未执行导致的资源泄漏,如System.exit()、线程被强制终止或死循环等场景。

finally 块里再抛异常,不是并发问题,而是异常覆盖问题——它不涉及多线程资源竞争,但会直接掩盖原始异常,导致故障根因丢失。
finally 抛异常的本质是“异常覆盖”,不是线程安全问题
当 try 块中已发生异常(比如数据库操作失败),而 finally 块在关闭连接时又抛出新异常(比如网络抖动导致 close() 失败),JVM 会丢弃 try 中的原始异常,只向上抛出 finally 中的异常。这不是多个线程争抢同一资源引发的竞争,而是 JVM 异常传播机制的确定性行为:后发生的 throw 优先级更高。
- 原始异常对象被 JVM 暂存,但未进入调用栈
- finally 执行 throw 时,JVM 清空暂存区,用新异常替代
- 旧异常失去引用,被 GC 回收,物理消失
真正需要防的是“资源未释放”和“锁未归还”
高并发下更危险的不是 finally 抛异常,而是 finally 根本没执行——比如分布式锁场景中,业务逻辑出错后没走到 finally,锁就一直挂着。
- 获取锁 → 业务异常 → finally 被跳过 → 锁超时前无法释放
- 促销流量涌入 → 多个请求卡在锁等待 → 数据库连接池耗尽 → 全站雪崩
- 这种失效不依赖线程调度,而是控制流中断导致的清理遗漏
推荐做法:用 try-with-resources + 显式 suppress
Java 7+ 应优先使用 try-with-resources 管理 AutoCloseable 资源,它会在异常传播前自动调用 close(),并把 close() 中的异常作为 suppressed exception 附加到主异常上,不丢失上下文。
- 避免手写 finally 关闭逻辑
- 若必须手动关闭(如自定义锁),在 finally 中捕获 close() 异常并用 addSuppressed() 保留原始异常
- 日志中同时打印主异常和 suppressed 异常,定位更准
注意 finally 不执行的几种真实失效场景
这些情况才会真正导致资源泄漏,且与并发强相关:
- System.exit():JVM 进程立即终止,finally 跳过(哪怕在多线程中调用也一样)
- 线程被强制 interrupt 或 kill:比如容器 OOM 后被 SIGKILL,finally 无机会运行
- 死循环或长时间阻塞:比如锁等待超时设置不合理,线程卡住,finally 永远不触发

















