synchronized在抛出未捕获异常时一定会释放锁,这是JVM层面的硬性保障;编译时生成两个monitorexit指令(正常路径和异常表覆盖路径),确保RuntimeException、Error及逃逸的受检异常均触发计数器减1与_owner清空,不依赖finally或中断处理。

synchronized 在抛出未捕获异常(UncaughtException)时一定会释放锁,这是 JVM 层面的硬性保障,不是靠程序员写 finally 或 catch 实现的。
底层靠两个 monitorexit 指令兜底
JVM 编译 synchronized 代码块时,会生成两套退出逻辑:
- 一个 monitorexit 对应正常执行完同步块的路径
- 另一个 monitorexit 被写进方法的异常表(exception table),覆盖所有可能抛出异常的字节码范围
- 只要线程因未捕获异常退出同步作用域,JVM 就会触发异常路径上的 monitorexit,将 Monitor 计数器减 1;归零后 _owner 清空,锁彻底释放
所有未捕获异常都触发释放
不管异常类型是什么,只要没被 try-catch 拦住,就会走异常释放路径:
- RuntimeException 及其子类(如 NullPointerException、ArrayIndexOutOfBoundsException)
- Error 类型(如 OutOfMemoryError、StackOverflowError),哪怕 JVM 已严重异常,仍执行 monitorexit
- 逃逸出 synchronized 块边界的受检异常(Checked Exception),比如 IOException、SQLException
不依赖 finally,也不怕中断
这个机制完全脱离 Java 代码逻辑控制:
立即学习“Java免费学习笔记(深入)”;
- 同步块里没写 catch、也没写 finally,照样释放
- 线程被 interrupt() 中断,抛出 InterruptedException 且未捕获,锁依然释放
- 和 Lock 不同:ReentrantLock 的 unlock() 是普通方法调用,漏写或写在 throw 前就可能死锁;synchronized 的释放是字节码级强制行为,无法绕过
方法级 synchronized 同样适用
修饰实例方法或静态方法时,原理一致:
- 方法通过 ACC_SYNCHRONIZED 标志标记,JVM 在方法入口隐式插入 monitorenter,在方法出口(含异常出口)插入 monitorexit
- 哪怕方法体第一行就 throw new RuntimeException("boom"),锁也会在异常传播前完成释放
- 这意味着其他线程能立刻竞争该对象(或 Class)的锁,不会因异常卡死


















