Skip to content
清晨的一缕阳光
返回

synchronized 底层原理详解

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,通过monitorentermonitorexit字节码实现:

synchronized (obj) {
    // monitorenter
    // 业务逻辑
    // monitorexit
}

Monitor 内部结构

ObjectMonitor
├── Owner - 当前持有锁的线程
├── EntryList - 竞争锁失败的线程队列(BLOCKED)
├── WaitSet - 调用 wait() 的线程队列(WAITING)
├── Recursions - 锁的重入次数
└── Count - 等待线程数

二、锁状态与升级机制

2.1 锁状态对比

锁状态Mark Word 标识特性性能
无锁0无竞争最高
偏向锁1单线程访问
轻量级锁00CAS 自旋
重量级锁10操作系统 Mutex

2.2 锁升级过程

flowchart LR
    A[无锁] -->|"首次访问"| B[偏向锁]
    B -->|"其他线程竞争"| C[轻量级锁]
    C -->|"CAS自旋失败"| D[重量级锁]
    D -->|"竞争降低"| C[轻量级锁]
    

升级触发条件

  1. 偏向锁 → 轻量级锁:其他线程尝试竞争锁
  2. 轻量级锁 → 重量级锁:CAS 自旋失败(超过阈值)
  3. 重量级锁回退:竞争降低时,可能降级(需安全点)

三、关键实现细节

3.1 偏向锁优化

// 首次获取锁时,通过 CAS 将线程 ID 写入 Mark Word
// 后续访问无需同步,直接执行

撤销偏向锁

3.2 轻量级锁实现

// 线程在栈帧中创建 Lock Record
// 通过 CAS 将对象头指向 Lock Record
// 自旋等待(默认 10 次)

自旋优化

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

特性synchronizedReentrantLock
实现层级JVM 内置(字节码)用户态(Lock 接口)
公平性非公平(默认)支持公平/非公平
条件变量wait/notifyCondition 接口
中断响应不支持支持 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 核心要点:

  1. 底层实现:对象头 Mark Word + Monitor
  2. 锁升级:偏向锁 → 轻量级锁 → 重量级锁
  3. JVM 优化:锁消除、锁粗化、自适应自旋
  4. 可重入性:同一线程可多次获取同一锁
  5. 非公平性:新线程可抢占锁(提升吞吐量)

JDK 1.8 后 synchronized 性能大幅提升,优先使用 synchronized,特殊场景再用 ReentrantLock。


分享这篇文章到:

上一篇文章
AQS 深度解析
下一篇文章
Java 线程状态详解