synchronized 锁的释放机制是通过 JVM 底层的 Monitor 锁模型和 计数器机制 实现的。以下是具体的释放逻辑和触发条件:
一、锁释放的触发条件
-
正常执行结束
当线程执行完synchronized
修饰的代码块或方法时,JVM 会自动调用monitorexit
指令释放锁。
• 同步代码块:通过monitorenter
和monitorexit
指令显式控制锁的获取与释放。
• 同步方法:通过ACC_SYNCHRONIZED
标志隐式触发锁的获取和释放,方法结束时自动释放锁。 -
异常退出
如果在synchronized
代码块或方法中抛出未捕获的异常,JVM 会执行monitorexit
指令释放锁。
• 编译时会为同步代码块生成两个monitorexit
指令:一个用于正常退出,另一个用于异常路径。 -
显式调用
wait()
线程在synchronized
代码块内调用wait()
方法时,会暂时释放锁并进入等待状态,直到其他线程调用notify()
/notifyAll()
唤醒它。
二、锁释放的核心机制
-
计数器递减
• 每个对象关联一个 Monitor 锁,内部维护一个计数器(_count
)。
• 线程每次进入synchronized
代码块时计数器加 1,退出时减 1。
• 当计数器归零时,锁完全释放,其他线程可竞争获取。 -
Monitor 对象的状态管理
• 持有锁的线程(_owner
):释放锁后,_owner
置为null
,计数器归零。
• 等待队列(_EntryList
和_WaitSet
):锁释放后,JVM 会从_EntryList
或_WaitSet
中唤醒线程重新竞争锁。
三、不同场景的锁释放示例
1. 同步代码块
public void method() {
synchronized (this) {
// 代码逻辑
} // 此处自动执行 monitorexit 释放锁
}
• 无论正常结束还是抛出异常,monitorexit
都会触发锁释放。
2. 同步方法
public synchronized void method() {
// 代码逻辑
} // 方法结束自动释放锁
• 通过 ACC_SYNCHRONIZED
标志隐式管理锁,无需显式字节码指令。
3. 异常场景
public void method() {
synchronized (this) {
throw new RuntimeException(); // 触发异常,自动释放锁
}
}
• 即使未捕获异常,JVM 也会执行 monitorexit
指令释放锁。
四、锁释放的底层实现(字节码层面)
-
同步代码块
编译后生成monitorenter
和两个monitorexit
(正常退出和异常退出)指令:public void method(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter // 获取锁 4: ... // 业务代码 13: monitorexit // 正常退出释放锁 14: goto 20 17: aload_1 18: monitorexit // 异常退出释放锁 19: athrow 20: return
-
同步方法
方法访问标志包含ACC_SYNCHRONIZED
,JVM 在方法入口和出口隐式管理锁。
五、注意事项
-
不会释放锁的操作
•Thread.sleep()
、Thread.yield()
不会释放锁。
• 线程挂起(如suspend()
)也不会释放锁。 -
可重入性
• 同一线程可多次获取锁(计数器递增),需对应次数的退出操作才能完全释放。
总结
synchronized 锁的释放依赖于 JVM 的 Monitor 模型和计数器机制,通过以下方式触发:
- 代码块或方法正常结束。
- 未捕获异常抛出。
- 显式调用
wait()
。
其底层通过monitorexit
指令或隐式标志确保锁的正确释放,保障线程安全。