synchronized实现原理详解
synchronized 关键字的实现原理
synchronized
是 Java 中最基础的同步机制,其实现原理涉及多层次的优化和设计。让我们深入探讨其实现机制:
一、JVM 层面的实现基础
1. Java 对象头 (Object Header)
每个 Java 对象在内存中由三部分组成:
- 对象头 (Header):包含锁信息和 GC 信息
- 实例数据 (Instance Data):对象字段值
- 对齐填充 (Padding):内存对齐使用
其中对象头是实现 synchronized 的关键:
|-------------------------------------------------------|
| Mark Word (64 bits) |
|-------------------------------------------------------|
| 锁状态 | 61 bits 内容 | 锁标志位 |
|-------------------------------------------------------|
| 无锁状态 | unused|hashCode|分代年龄| 01 |
| 偏向锁 | 线程ID| Epoch |分代年龄| 01 |
| 轻量级锁 | 指向栈中锁记录的指针 | 00 |
| 重量级锁 | 指向Monitor的指针 | 10 |
| GC标记 | 空 | 11 |
|-------------------------------------------------------|
2. Monitor(管程)
每个 Java 对象都与一个 Monitor 关联:
- 当线程进入 synchronized 代码块时,会尝试获取对象的 Monitor
- Monitor 的主要结构:
ObjectMonitor { void* _header; // 对象头 void* _owner; // 持有锁的线程 intptr_t _recursions; // 重入次数 ObjectWaiter* _EntryList; // 等待队列 ObjectWaiter* _WaitSet; // wait() 等待队列 // ... }
二、锁升级过程(锁膨胀)
JVM 为减少锁操作开销,设计了锁升级机制:
1. 无锁状态
- 新创建的对象初始状态
- 锁标志位:01
2. 偏向锁 (Biased Lock)
- 适用场景:单线程重复访问同步块
- 实现原理:
- 通过 CAS 将线程 ID 写入 Mark Word
- 同一线程再次进入时只需检查线程 ID
- 锁标志位:01(与无锁相同,但偏向位为1)
- 优点:零成本加锁解锁
- 缺点:有竞争时会升级
3. 轻量级锁 (Lightweight Lock)
- 适用场景:多线程交替执行,无实际竞争
- 实现原理:
- 在栈帧创建 Lock Record
- 将对象 Mark Word 复制到 Lock Record(Displaced Mark Word)
- CAS 将对象头指向 Lock Record
- 成功:获取锁;失败:自旋或升级
- 锁标志位:00
4. 重量级锁 (Heavyweight Lock)
- 适用场景:多线程竞争激烈
- 实现原理:
- 指向 Monitor 对象的指针
- 未获取锁的线程进入阻塞状态
- 依赖操作系统互斥量(mutex)
- 锁标志位:10
- 特点:线程阻塞/唤醒涉及用户态-内核态切换,开销大
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象)
_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。
三、锁升级过程示意图
sequenceDiagram
participant T as 线程
participant OM as ObjectMonitor
participant OS as 操作系统
T->>OM: 尝试通过CAS获取锁
alt 获取成功
OM-->>T: 设置_owner=当前线程
else 获取失败
OM->>OM: 自旋尝试 (轻量级锁)
alt 自旋成功
OM-->>T: 获取锁
else 自旋失败
OM->>OM: 将线程加入_EntryList
OM->>OS: 调用pthread_mutex_lock()
OS->>OS: 线程进入阻塞状态
OS-->>OM: 当锁可用时唤醒
OM-->>T: 获取锁
end
end
四、底层实现细节
1. 字节码层面
- 同步方法:
ACC_SYNCHRONIZED
标志 - 同步块:
monitorenter
和monitorexit
指令
2. 操作系统层面
- 重量级锁使用 pthread 互斥锁:
pthread_mutex_lock(&monitor->mutex); // 临界区代码 pthread_mutex_unlock(&monitor->mutex);
3. 优化技术
- 自旋锁:轻量级锁竞争时,线程执行忙循环(默认10次)
- 适应性自旋:根据历史成功率动态调整自旋次数
- 锁消除:JIT 编译器消除不可能存在竞争的锁
- 锁粗化:合并连续的同步块减少锁操作
五、性能特点对比
锁类型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加解锁几乎无开销 | 撤销开销大 | 单线程重复访问 |
轻量级锁 | 避免线程阻塞 | 自旋消耗CPU | 多线程交替执行 |
重量级锁 | 不消耗CPU等待 | 线程切换开销大 | 高竞争场景 |
六、最佳实践
- 优先使用
java.util.concurrent
包中的显式锁 - 减少同步块范围(锁粒度细化)
- 避免在循环内使用同步
- 考虑使用
ThreadLocal
避免共享 - 高并发场景慎用 synchronized
理解 synchronized 的实现原理有助于:
- 编写高性能并发代码
- 合理选择同步机制
- 诊断死锁和性能问题
- 深入理解 JVM 并发模型
🎯 总结
synchronized的实现原理包括:
- 对象头机制:基于Mark Word存储锁信息
- Monitor模型:重量级锁的实现基础
- 锁升级策略:从偏向锁→轻量级锁→重量级锁
- 字节码支持:monitorenter/monitorexit指令
- JVM优化:锁消除、锁粗化等优化策略