90%的人能说出偏向锁、轻量级锁、重量级锁 ,但说不清楚为什么要升级、什么时候升级、底层怎么实现、性能差在哪。
前言:一句话说清锁升级的意义
synchronized不是一上来就加重量级锁(OS互斥锁)!
JVM非常聪明,它会根据并发竞争程度,自动逐步升级锁 :无锁 → 偏向锁 → 轻量级锁 → 重量级锁 ,这就是锁膨胀(Lock Inflation) 。
目的只有一个:在并发越低的场景,锁开销越小;竞争激烈才动用重锁。
一、先搞懂:锁信息存在哪里?
所有锁状态,都存在对象头(Mark Word)里。你加锁的对象,本身就存储了:
- 谁持有锁
- 锁是什么状态
- 线程ID
- 指向锁记录的指针
- 指向重量级锁的指针
锁升级 = Mark Word里的内容不断改写。
二、锁升级全流程
1)无锁状态(No Lock)
- 没有线程竞争
- 对象正常创建
Mark Word存储: 对象哈希码、分代年龄。
2)偏向锁(Biased Lock)------ 第一个线程进来
触发条件
只有一个线程反复加锁,无竞争。
JVM 做了什么
- 锁直接偏向这个线程
- 不在走CAS,不做任何同步操作
- 只需要判断对象头的线程ID是否是自己
效率
几乎无开销,和不加锁差不多快。
什么时候升级?
当第二个线程尝试竞争锁时,偏向锁立刻撤销 → 升级为轻量级锁。
3)轻量级锁(Lightweight Lock)------ 有少量竞争
触发条件
多个线程交替加锁,极少同时竞争。
底层实现
- 线程在栈帧创建Lock Record
- 用CAS尝试替换对象头
- 成功 = 加锁成功
- 失败 = 自旋(空转)
优点
不进入内核态,不阻塞线程,速度快。
什么时候升级?
- 自旋次数达到阈值(JVM自动控制)
- 有线程长时间持有锁
- 有多个线程同时竞争
→ 升级为重量级锁
4)重量级锁(Heavyweight Lock)------ 高并发竞争
触发条件
激烈竞争、多线程同时抢锁。
底层实现
- 向操作系统内核申请互斥锁(Mutex)
- 拿不到锁的线程进入阻塞状态(BLOCKED)
- 线程从用户态 → 内核态(开销巨大)
缺点
开销最大、最慢、线程上下文切换重。
三、一张图看懂锁升级流程
无锁
↓ (第一个线程加锁)
偏向锁 【只给一个线程用】
↓ (第二个线程竞争)
偏向锁撤销
↓
轻量级锁 【CAS + 自旋】
↓ (自旋失败/竞争激烈)
重量级锁 【OS 互斥锁 + 线程阻塞】
四、三种锁的核心对比
| 锁 | 实现 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|
| 偏向锁 | Mark Word 存线程ID | 无竞争就不做同步 | 最快,几乎零成本 | 多线程竞争会撤销(开销大) | 单线程反复执行同步块 |
| 轻量级锁 | CAS + 自旋 | 不阻塞,空转重试 | 响应快、不挂起线程 | 自旋消耗CPU | 低竞争、锁持有时间短 |
| 重量级锁 | OS 互斥锁 | 阻塞线程、内核调度 | 不消耗CPU | 线程阻塞、上下文切换 | 高并发、锁持有时间长 |
五、最关键的6个底层真相(90%人不知道)
1)偏向锁在JDK 15+默认关闭
因为高并发场景下,偏向锁撤销开销太大 。
JDK15以后默认直接启动到轻量级锁。
2)轻量级锁的自旋不是固定次数
JDK7以后自适应自旋:
- 之前成功 → 多自旋
- 之前失败 → 少自旋
- 智能、自动优化
3)锁只能升级,不能降级
偏向 → 轻量 → 重量 ,一旦升级,不能退回去。
4)synchronized 是可重入锁
同一线程可以反复加锁,计数器+1。
5)wait() / notify() 必须用重量级锁
只有重量级锁才支持等待/唤醒。
6)锁消除、锁粗化都是JIT优化
- 锁消除:未逃逸对象的锁直接删掉
- 锁粗化:把连续加锁合并成一个大锁
这也是synchronized越来越快的原因。
六、生产实战意义
1)锁持有时间越短越好
轻量级锁自旋效率极高,锁一长,必膨胀成重量级锁。
2)高并发服务建议关闭偏向锁
-XX:-UseBiasedLocking
避免高并发下偏向锁撤销导致性能暴跌。
3)不要让锁竞争激烈
竞争激烈 = 重量级锁 = 线程大量阻塞 = TPS暴跌。
4)局部变量、私有对象 = 锁永远不会升级
JIT直接锁消除,性能无敌。
七、总结
synchronized锁升级流程:
- 无锁
- 偏向锁:单线程无竞争,直接偏向
- 轻量级锁:多线程交替执行,CAS + 自旋
- 重量级锁:竞争激烈,OS内核互斥锁
核心思想:无竞争不加锁,少竞争用轻锁,高竞争才用重锁。
synchronized从来不是笨重的重量级锁。它是JVM精心设计的智能自适应锁 。理解锁升级,才算真正掌握 Java 高并发。