环境背景:Java对象头Mark Word实现锁状态,锁一共有4种状态:无锁 → 偏向锁 → 轻量级锁 → 重量级锁,只能升级不能降级。JDK1.6引入偏向锁,JDK15默认禁用偏向锁(-XX:+UseBiasedLocking默认false)。
一、synchronized 为什么可以锁升级?
锁升级本质:JDK1.6对synchronized做自适应自旋+锁状态分层优化,依据竞争激烈程度,逐步切换锁实现,平衡性能开销。 对象头Mark Word存储锁标记位: |锁状态|标记位|说明| |---|---|---| |无锁|01|没有线程竞争| |偏向锁|01|偏向某一个线程,单线程优先| |轻量级锁|00|自旋,多线程交替访问,竞争不激烈| |重量级锁|10|向OS申请互斥锁,阻塞线程,竞争激烈|
1)偏向锁
场景:大多数情况锁总是被同一个线程获取,几乎没有竞争。
- 线程第一次获取锁:CAS把Mark Word里线程ID设置为当前线程,标记为偏向锁。不需要每次加锁解锁做CAS操作,后续该线程再次进入同步块只做简单判断,性能极高。
- 没有真正加锁,只是标记“这个锁归你”。
缺陷:一旦出现另外线程竞争,偏向锁撤销(revoke)代价很大,需要全局安全点STW,暂停持有锁的线程。
2)轻量级锁
场景:多线程交替获取锁,竞争不激烈,不会同时抢锁。 当有第二个线程来抢偏向锁,偏向锁撤销,升级为轻量级锁。
- 线程在栈帧创建锁记录LR,CAS把对象Mark Word替换指向LR;CAS成功拿到锁。
- 拿不到锁的线程自适应自旋循环,不阻塞,不调用操作系统内核。
- 自旋有次数限制,不会无限自旋,消耗CPU。
适用:线程短时间交替持有锁。如果两个线程同时抢锁(真竞争),自旋失败,升级重量级锁。
3)重量级锁
场景:多个线程同时竞争锁,竞争激烈。 依赖操作系统mutex互斥锁。
- 竞争失败的线程直接阻塞,放入等待队列,内核态完成线程挂起唤醒。
- 用户态→内核态切换,开销大。
✅为什么只能升级,不能降级?
- 降级需要STW,开销巨大。已经升级到重量级锁之后,如果再降级回轻量/偏向锁,需要遍历阻塞队列、重置Mark Word,要安全点暂停所有线程,得不偿失。
- JVM设计假设:一旦发生激烈竞争,大概率后续竞争依旧激烈,没有必要退回到低层级锁。
重点:重量级锁不会降级;轻量级锁在无竞争时可以回到无锁,但不会回到偏向锁。偏向锁一旦撤销,该对象再也不会进入偏向锁状态。
总结锁升级完整流程: 无锁 → 单线程反复获取 → 偏向锁 出现第二个线程竞争 → 撤销偏向锁 → 轻量级锁 多线程同时争抢,自旋耗尽 → 重量级锁
二、JDK15 默认关闭偏向锁 UseBiasedLocking=false
可以手动开启:
-XX:+UseBiasedLocking,但是官方不推荐。
1)偏向锁的核心痛点
偏向锁收益建立在:锁长期被同一个线程占用,几乎没有竞争。 但是它的撤销(revoke)代价极高:
- 撤销偏向锁必须走到 STW全局安全点,暂停持有锁的线程,修正对象头Mark Word。
- 批量重偏向、批量撤销:当大量对象偏向锁被撤销,会触发批量处理;高并发场景下,批量撤销会带来明显的STW停顿。
2)现代业务环境变化
- 现在高并发多线程程序越来越多,很多锁会被多个线程访问,真正适合偏向锁的场景变少。很多锁刚偏向,马上就遇到竞争,立刻触发撤销,不仅没有收益,反而引入STW开销。
- 轻量级锁的CAS性能已经优化的非常好。对于单线程场景,轻量级锁CAS开销已经很小,偏向锁带来的性能提升微乎其微,但是一旦竞争,惩罚代价很大。
3)官方考量(OpenJDK官方说明)
- 偏向锁代码复杂,维护成本高,很多bug都来源于偏向锁逻辑。
- 真实业务中,偏向锁的性能收益经常被它撤销带来的STW抵消甚至反亏。
- 保留开关,不删除该特性,如果你的业务确定大量锁始终只有一个线程访问,可以手动打开。
JDK15变更:只是默认关闭,不是移除偏向锁。
4)补充对比
- JDK1.6~14:
UseBiasedLocking=true默认开启 - JDK15+:
UseBiasedLocking=false默认关闭
关闭偏向锁之后锁路径:无锁 → 轻量级锁 → 重量级锁,跳过偏向锁这一步。
三、常见面试追问
-
Q:关闭偏向锁之后,单线程拿synchronized会不会变慢? A:会走轻量级锁CAS。现代CPU CAS指令极快,绝大多数场景性能差距很小,避免了偏向锁撤销STW风险。
-
Q:偏向锁一定比轻量级锁快? A:只有完全无竞争,同一个线程反复拿锁才快。一旦出现竞争,撤销的STW开销会远大于收益。
-
Q:锁可以降级吗? A:重量级锁不会降级;轻量级锁解锁后回到无锁,但不会回到偏向锁。偏向锁撤销后对象永久不再偏向。
-
Q:什么是批量重偏向、批量撤销?
当一个类大量对象发生偏向锁撤销,JVM判断:可能是线程换了,就做批量重偏向,把这些对象偏向新线程; 如果撤销继续增多,触发批量撤销,这个类所有新创建对象直接禁止偏向。
注意:本文归作者所有,未经作者允许,不得转载