synchronized 的底层实现机制,核心就是 JDK 1.6 之后的锁升级(锁膨胀)过程——JVM 为了优化锁性能,设计了四级锁状态(无锁、偏向锁、轻量级锁、重量级锁),会根据多线程竞争情况自动升级,不会主动降级,这是 synchronized 高效的核心。

核心优化逻辑:无竞争用偏向锁,轻竞争用自旋轻量锁,强竞争用内核重量级锁。

一、对象头(锁的载体)

synchronized 锁的是对象,所有锁信息都存在 对象头(Mark Word) 里,这是锁升级的基础。

Java 对象内存布局 = 对象头 + 实例数据 + 对齐填充。

Mark Word(关键) 存储:

  • 哈希码(HashCode)
  • GC 分代年龄
  • 锁状态标志
  • 持有锁的线程 ID
  • 指向锁记录 / 重量级锁的指针
锁状态 存储内容 标志位
无锁 哈希码、分代年龄 01
偏向锁 线程 ID、分代年龄 01
轻量级锁 指向栈中锁记录的指针 00
重量级锁 指向互斥量的指针 10

二、锁升级完整流程(核心)

顺序:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。只会升级,不会降级(JDK 15+ 有优化但主流场景仍遵循此规则)

无锁(01)
   ↓ 第一个线程访问
偏向锁(01,存储线程ID)
   ↓ 第二个线程竞争
轻量级锁(00,自旋CAS)
   ↓ 自旋失败/多线程竞争
重量级锁(10,内核互斥锁)

1. 无锁状态

  • 没有任何线程竞争,对象处于正常状态
  • Mark Word 存储:哈希码 + 分代年龄 + 01(标志位)

2. 偏向锁(第一个线程获取锁)

适用场景:只有一个线程反复执行同步代码,无竞争

原理:

JVM 默认开启偏向锁,第一个线程访问同步代码时

  1. 检查对象头 Mark Word 是否为可偏向状态(01)
  2. 通过 CAS 操作把自己的线程 ID写入对象头
  3. 后续该线程再次进入同步块,无需任何 CAS、无需加锁,直接放行

特点:

  • 开销极低,几乎和无锁代码一样快
  • 一旦有第二个线程参与竞争,偏向锁立刻撤销,升级为轻量级锁

3. 轻量级锁(自旋锁,多线程交替执行)

适用场景:少量线程竞争,锁持有时间短,线程交替执行

原理:

  1. 偏向锁撤销后,JVM 在当前线程栈帧中创建 锁记录(Lock Record)
  2. 线程尝试用 CAS 将对象头 Mark Word 替换为指向锁记录的指针
  3. CAS 成功:当前线程获取轻量级锁
  4. CAS 失败:说明有其他线程竞争,当前线程自旋(循环尝试 CAS),不阻塞

特点:

  • 不使用内核态的互斥锁,减少线程上下文切换开销
  • 自旋是忙等待,消耗 CPU,适合短时间持有锁的场景
  • 自旋达到一定次数 / 阈值,升级为重量级锁

4. 重量级锁(多线程激烈竞争)

适用场景:大量线程竞争,锁持有时间长

原理:

  1. 轻量级锁自旋失败后,升级为重量级锁
  2. 对象头 Mark Word 指向 操作系统互斥量(Mutex/Monitor)
  3. 竞争失败的线程会进入阻塞状态,释放 CPU 资源
  4. 锁释放后,唤醒阻塞线程,重新竞争

特点:

  • 线程阻塞 / 唤醒需要用户态 ↔ 内核态切换,开销最大
  • 不消耗 CPU(线程休眠),适合高竞争、长持有锁场景

5. 三种锁的对比

锁类型 优点 缺点 适用场景
偏向锁 加锁解锁无开销,比无锁略快 多线程竞争时,撤销锁有开销 单线程反复执行同步块
轻量级锁 自旋不阻塞,无线程切换开销 自旋消耗 CPU 线程交替执行,锁持有时间短
重量级锁 线程阻塞不消耗 CPU 阻塞 / 唤醒开销极大 高并发竞争,锁持有时间长

三、补充知识点

1. 偏向锁的撤销

偏向锁只有遇到其他线程竞争时才会撤销,撤销需要等待 全局安全点(STW),这是偏向锁唯一的性能损耗点。

2. 自旋锁的优化

JDK 1.7+ 后,自旋次数由 JVM 自适应控制(根据历史锁竞争情况动态调整),不再固定次数。

3. 为什么要设计锁升级?

  • 无锁 / 偏向锁:解决无竞争场景
  • 轻量级锁:解决短时间竞争场景
  • 重量级锁:解决激烈竞争场景用最低成本的锁应对不同竞争强度,最大化提升性能

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐