synchronized 底层(锁升级)
·
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 默认开启偏向锁,第一个线程访问同步代码时:
- 检查对象头 Mark Word 是否为可偏向状态(01)
- 通过 CAS 操作把自己的线程 ID写入对象头
- 后续该线程再次进入同步块,无需任何 CAS、无需加锁,直接放行
特点:
- 开销极低,几乎和无锁代码一样快
- 一旦有第二个线程参与竞争,偏向锁立刻撤销,升级为轻量级锁
3. 轻量级锁(自旋锁,多线程交替执行)
适用场景:少量线程竞争,锁持有时间短,线程交替执行
原理:
- 偏向锁撤销后,JVM 在当前线程栈帧中创建 锁记录(Lock Record)
- 线程尝试用 CAS 将对象头 Mark Word 替换为指向锁记录的指针
- CAS 成功:当前线程获取轻量级锁
- CAS 失败:说明有其他线程竞争,当前线程自旋(循环尝试 CAS),不阻塞
特点:
- 不使用内核态的互斥锁,减少线程上下文切换开销
- 自旋是忙等待,消耗 CPU,适合短时间持有锁的场景
- 自旋达到一定次数 / 阈值,升级为重量级锁
4. 重量级锁(多线程激烈竞争)
适用场景:大量线程竞争,锁持有时间长
原理:
- 轻量级锁自旋失败后,升级为重量级锁
- 对象头 Mark Word 指向 操作系统互斥量(Mutex/Monitor)
- 竞争失败的线程会进入阻塞状态,释放 CPU 资源
- 锁释放后,唤醒阻塞线程,重新竞争
特点:
- 线程阻塞 / 唤醒需要用户态 ↔ 内核态切换,开销最大
- 不消耗 CPU(线程休眠),适合高竞争、长持有锁场景
5. 三种锁的对比
| 锁类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 偏向锁 | 加锁解锁无开销,比无锁略快 | 多线程竞争时,撤销锁有开销 | 单线程反复执行同步块 |
| 轻量级锁 | 自旋不阻塞,无线程切换开销 | 自旋消耗 CPU | 线程交替执行,锁持有时间短 |
| 重量级锁 | 线程阻塞不消耗 CPU | 阻塞 / 唤醒开销极大 | 高并发竞争,锁持有时间长 |
三、补充知识点
1. 偏向锁的撤销
偏向锁只有遇到其他线程竞争时才会撤销,撤销需要等待 全局安全点(STW),这是偏向锁唯一的性能损耗点。
2. 自旋锁的优化
JDK 1.7+ 后,自旋次数由 JVM 自适应控制(根据历史锁竞争情况动态调整),不再固定次数。
3. 为什么要设计锁升级?
- 无锁 / 偏向锁:解决无竞争场景
- 轻量级锁:解决短时间竞争场景
- 重量级锁:解决激烈竞争场景用最低成本的锁应对不同竞争强度,最大化提升性能。
更多推荐




所有评论(0)