synchronized 底层原理详解
synchronized 通过 JVM 对象头和 Monitor 实现线程同步,结合锁升级机制动态优化性能。
一、核心机制:对象头与 Monitor
1.1 对象头(Object Header)
每个 Java 对象在内存中的头部结构:
对象头 (64 位 JVM)
├── Mark Word (64 位)
│ ├── 锁状态 (2 位)
│ ├── 线程 ID (54 位)
│ ├── GC 分代年龄 (4 位)
│ └── 哈希码/锁信息
├── Klass Pointer (32 位) - 指向类元数据
└── 数组长度 (可选)
Mark Word 结构(64 位):
|-------------------------------------------------------|--------------------|
| hashcode:31 | age:4 | 锁标志位:2 |
|-------------------------------------------------------|--------------------|
| 偏向锁:线程 ID:54 | 时间戳:9 | 01 | 偏向锁标志 |
|-------------------------------------------------------|--------------------|
| 轻量级锁:指向 Lock Record 的指针 | 00 | |
|-------------------------------------------------------|--------------------|
| 重量级锁:指向 Monitor 的指针 | 10 | |
|-------------------------------------------------------|--------------------|
1.2 Monitor(监视器锁)
每个对象关联一个 Monitor,通过monitorenter和monitorexit字节码实现:
synchronized (obj) {
// monitorenter
// 业务逻辑
// monitorexit
}
Monitor 内部结构:
ObjectMonitor
├── Owner - 当前持有锁的线程
├── EntryList - 竞争锁失败的线程队列(BLOCKED)
├── WaitSet - 调用 wait() 的线程队列(WAITING)
├── Recursions - 锁的重入次数
└── Count - 等待线程数
二、锁状态与升级机制
2.1 锁状态对比
| 锁状态 | Mark Word 标识 | 特性 | 性能 |
|---|---|---|---|
| 无锁 | 0 | 无竞争 | 最高 |
| 偏向锁 | 1 | 单线程访问 | 高 |
| 轻量级锁 | 00 | CAS 自旋 | 中 |
| 重量级锁 | 10 | 操作系统 Mutex | 低 |
2.2 锁升级过程
flowchart LR
A[无锁] -->|"首次访问"| B[偏向锁]
B -->|"其他线程竞争"| C[轻量级锁]
C -->|"CAS自旋失败"| D[重量级锁]
D -->|"竞争降低"| C[轻量级锁]
升级触发条件:
- 偏向锁 → 轻量级锁:其他线程尝试竞争锁
- 轻量级锁 → 重量级锁:CAS 自旋失败(超过阈值)
- 重量级锁回退:竞争降低时,可能降级(需安全点)
三、关键实现细节
3.1 偏向锁优化
// 首次获取锁时,通过 CAS 将线程 ID 写入 Mark Word
// 后续访问无需同步,直接执行
撤销偏向锁:
- 检测到其他线程竞争
- 需要安全点暂停其他线程
- 恢复对象头为无锁状态
3.2 轻量级锁实现
// 线程在栈帧中创建 Lock Record
// 通过 CAS 将对象头指向 Lock Record
// 自旋等待(默认 10 次)
自旋优化:
- JDK 6:固定 10 次
- JDK 7+:自适应自旋(根据历史成功率)
3.3 重量级锁
// 依赖操作系统 Mutex 实现
// 涉及用户态 ↔ 内核态切换
// 线程阻塞/唤醒
四、JVM 优化策略
4.1 锁消除
JIT 编译器检测无竞争场景时,直接消除锁:
// 优化前
public void method() {
StringBuffer sb = new StringBuffer();
sb.append("a"); // 内部 synchronized
sb.append("b");
}
// 优化后:消除 synchronized
4.2 锁粗化
合并相邻同步块,减少锁请求次数:
// 优化前
synchronized (obj) { append("a"); }
synchronized (obj) { append("b"); }
// 优化后
synchronized (obj) {
append("a");
append("b");
}
4.3 自适应自旋
根据历史自旋成功率动态调整自旋次数:
自旋次数 = f(上次自旋成功率,当前竞争情况)
五、synchronized vs ReentrantLock
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现层级 | JVM 内置(字节码) | 用户态(Lock 接口) |
| 公平性 | 非公平(默认) | 支持公平/非公平 |
| 条件变量 | wait/notify | Condition 接口 |
| 中断响应 | 不支持 | 支持 lockInterruptibly() |
| 性能 | JDK 1.8+ 优化后高 | 高并发场景更优 |
| 使用便捷 | 简单(自动加解锁) | 需手动 try/finally |
六、使用注意事项
6.1 避免死锁
// ❌ 错误示例:不同顺序获取锁
// 线程 A: synchronized(A) → synchronized(B)
// 线程 B: synchronized(B) → synchronized(A)
// ✅ 正确做法:固定顺序
// 所有线程都按 A → B 顺序获取锁
6.2 锁粒度控制
// ❌ 锁粒度过大
synchronized (this) {
// 大量无关代码
}
// ✅ 缩小锁范围
synchronized (lock) {
// 仅同步关键代码
}
6.3 可重入性
// synchronized 支持可重入
public synchronized void method1() {
method2(); // 同一线程可再次获取锁
}
public synchronized void method2() {
// ...
}
七、总结
synchronized 核心要点:
- 底层实现:对象头 Mark Word + Monitor
- 锁升级:偏向锁 → 轻量级锁 → 重量级锁
- JVM 优化:锁消除、锁粗化、自适应自旋
- 可重入性:同一线程可多次获取同一锁
- 非公平性:新线程可抢占锁(提升吞吐量)
JDK 1.8 后 synchronized 性能大幅提升,优先使用 synchronized,特殊场景再用 ReentrantLock。