synchronized
加锁的过程本质是通过 monitorenter
指令、对象头(Mark Word) 和 对象监视器(Monitor) 三者协作实现的。以下是它们之间的关联及详细过程:
1. 核心组件关系
2. 加锁详细过程
当线程执行到synchronized
代码块时,monitorenter
指令触发以下流程:
步骤1:检查对象头(Mark Word)
JVM首先读取对象的 Mark Word,根据锁标志位(
Lock Flag
)判断当前锁状态:无锁(01):尝试通过CAS操作将Mark Word更新为偏向锁或轻量级锁。
偏向锁(01):检查Mark Word中的线程ID是否为当前线程。
轻量级锁(00):通过CAS自旋尝试获取锁。
重量级锁(10):直接进入Monitor的竞争流程。
步骤2:锁升级与Mark Word修改
根据锁状态执行不同操作:
场景1:无锁 → 偏向锁
若对象未加锁(无锁状态),JVM尝试通过CAS将 线程ID 写入Mark Word,并设置偏向锁标志。
成功:线程直接进入同步代码(无需额外操作)。
失败:撤销偏向锁,升级为轻量级锁。
场景2:偏向锁 → 轻量级锁
若当前线程是偏向锁持有者:直接执行同步代码(可重入)。
若其他线程竞争:
撤销偏向锁:在全局安全点暂停原持有线程,恢复Mark Word为无锁状态。
升级为轻量级锁:在栈帧中创建锁记录(Lock Record),将Mark Word拷贝到锁记录。
通过CAS将对象头的Mark Word替换为指向锁记录的指针(锁记录地址 + 轻量级锁标志00)。
场景3:轻量级锁 → 重量级锁
若CAS自旋失败(竞争激烈):
锁膨胀:JVM分配一个Monitor对象,并将Mark Word指向Monitor的地址(锁标志变为10)。
关联Monitor:Mark Word中的指针指向Monitor,后续锁操作由Monitor管理。
步骤3:重量级锁与Monitor操作
当锁升级为重量级锁时,Monitor正式介入:
Monitor结构:包含以下关键字段:
Owner:持有锁的线程。
EntryList:竞争锁的阻塞线程队列。
WaitSet:调用
wait()
的等待线程队列。
获取锁:
线程尝试通过CAS将Monitor的Owner设置为自身。
若失败,线程进入EntryList阻塞(通过操作系统的互斥量)。
释放锁:Owner线程退出同步代码时,重置Owner并唤醒EntryList中的线程。
3. 关键过程示例
示例1:轻量级锁的加锁
synchronized (obj) {
// 代码块
}
线程A首次进入同步块:
Mark Word为无锁状态(01)。
CAS将Mark Word更新为轻量级锁(00),存储锁记录指针。
线程B竞争:
CAS失败,线程B自旋等待。
自旋超过阈值后,锁膨胀为重量级锁,Mark Word指向Monitor。
示例2:重量级锁的Monitor竞争
synchronized (obj) {
obj.wait(); // 调用wait()会释放锁,线程进入WaitSet
}
线程A持有Monitor的Owner字段。
调用
wait()
后,线程A释放Owner,进入WaitSet。线程B从EntryList中被唤醒,成为新的Owner。
4. 三者的关联总结
5. 性能与设计意义
锁升级机制:通过动态调整锁状态(偏向→轻量→重量),在无竞争时减少开销,高竞争时保证正确性。
Monitor的代价:重量级锁依赖操作系统互斥量,涉及用户态到内核态切换,性能较低。
优化建议:减少锁竞争(如缩小同步块)、使用
java.util.concurrent
工具类替代。
总结
synchronized
的加锁过程是 monitorenter
指令触发锁状态的判断与升级,通过修改对象头(Mark Word)记录锁信息,最终在重量级锁阶段由Monitor管理线程同步。这一机制在保证线程安全的同时,通过锁升级策略平衡了性能与可靠性。