synchronized锁升级
锁升级过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
# 各jdk版本锁
在jdk1.5版本(包含)之前,锁的状态只有两种状态:“无锁状态”和“重量级锁状态”,只要有线程访问共享资源对象,则锁直接成为重量级锁。因为加锁和释放锁的过程JVM的底层都是由操作系统mutex lock来实现的,其中会涉及上下文的切换(即用户态和内核态的转换),性能消耗极其高,所以在当时synchronized锁是公认的重量级锁。性能消耗非常大。后来JVM开发团队为解决性能问题,在jdk1.5版本中加入了JUC并发包,包下开发了很多Lock相关的锁。
jdk1.6版本后,对synchronized锁进行了优化,新加了“偏向锁”和“轻量级锁”,偏向锁是默认开启的,用来减少上下文的切换以提高性能,通过锁的升级来解决不同并发场景下的性能问题。所以锁就有了4种状态。
在 JDK 15 中,但由于偏向锁其撤销成本较高,偏向锁被默认禁用了。因为很多开发者发现,对于竞争激烈的场景,偏向锁带来的收益小于其撤销成本。
对象头(Mark Word)存储的内容:

解释:
无锁:对于共享资源,不涉及多线程的竞争访问,就不会加锁
# 锁升级流程
第一阶段:无锁 -> 偏向锁
触发条件:一个线程第一次进入同步块。
目标:在没有竞争的情况下,消除同步原语的开销。这个锁会“偏向”于第一个获取它的线程。
过程:
- 线程 A 访问同步块,检查锁标志位为 01,且偏向锁位为 0(表示可偏向)。
- 使用 CAS 操作,尝试将 Mark Word 中的 ThreadID 替换为自己的线程 ID。
- 如果 CAS 成功,线程 A 就持有了偏向锁。之后,只要线程 A 再次进入这个同步块,它只需要检查 Mark Word 中的 ThreadID 是否是自己,如果是,则无需任何同步操作,直接执行。
优点:对于同一个线程重复获取锁的场景,性能极高,几乎没有额外开销。
撤销:当有另一个线程 B 来尝试竞争这个锁时,偏向模式就宣告结束。JVM 需要撤销偏向锁。这个过程需要等待全局安全点,然后暂停持有偏向锁的线程 A,检查 A 是否存活或已退出同步块。然后根据情况,将锁升级为轻量级锁或恢复到无锁状态。
注意:在 JDK 6 之后,偏向锁是默认开启的,但由于其撤销成本较高,在 JDK 15 中,偏向锁被默认禁用了。因为很多开发者发现,对于竞争激烈的场景,偏向锁带来的收益小于其撤销成本。
第二阶段:偏向锁 -> 轻量级锁
- 触发条件:当偏向锁被撤销后,如果有线程竞争,但竞争是轻度的(即线程交替执行同步块,没有同时竞争)。
- 目标:在线程交替执行的场景下,避免直接使用重量级锁带来的用户态/内核态切换开销。
- 过程:
- 线程 A 持有偏向锁,线程 B 来竞争。
- 偏向锁被撤销。
- 线程 A 和 B 都会在自己的栈帧中创建一个名为 锁记录(Lock Record) 的空间。
- 线程 A 首先将当前 Mark Word 的内容复制到自己的锁记录中(称为 Displaced Mark Word)。
- 然后,线程 A 尝试用 CAS 操作将对象头的 Mark Word 替换为指向自己锁记录的指针。
- 如果成功,线程 A 就获取了轻量级锁。
- 此时,线程 B 也会进行同样的 CAS 操作来尝试获取锁,但会失败(因为 Mark Word 已被线程 A 修改)。失败后,线程 B 会自旋(循环重试 CAS)一小段时间来尝试获取锁。
- 如果在线程 B 自旋期间,线程 A 释放了锁(通过 CAS 将 Displaced Mark Word 写回对象头),那么线程 B 的 CAS 就可能成功,从而获取到锁。这样,线程间就没有发生阻塞。
- 优点:竞争的线程不会阻塞,通过自旋来提高响应速度。
升级:如果线程 B 自旋了一定次数后(JDK 6 引入了自旋适应的策略,一般默认锁10次 ?),还没能获取到锁,或者此时有第三个线程来竞争,说明竞争加剧了。此时,轻量级锁就会升级为重量级锁。
第三阶段:轻量级锁 -> 重量级锁
- 触发条件:轻量级锁竞争失败(自旋失败或有多线程竞争)。
- 目标:处理重度竞争的场景。
- 过程:
- JVM 会申请一个操作系统层面的 互斥量(Mutex Lock),也称为监视器锁(Monitor)。
- 将 Mark Word 的内容替换为指向这个互斥量的指针。
- 此时,所有未竞争到锁的线程(如线程 B),都会被挂起,进入操作系统的等待队列中。
- 等待锁被释放后,操作系统会负责唤醒这些被挂起的线程,让它们重新竞争。
- 缺点:开销巨大。涉及到操作系统内核态的切换,线程的挂起和唤醒都是很耗时的操作。
- 优点:在激烈竞争下,避免了 CPU 空转(无休止的自旋)。
