synchronized锁升级全流程解析
作者:互联网
2026-03-24
synchronized 锁升级全流程解析
在 Java 开发者的眼中,synchronized 曾经是“笨重”的代名词。早期版本的 synchronized 一上来就直接调用操作系统的底层同步机制,导致线程上下文切换频繁,效率极低。
但在 Java 6 之后,HotSpot 虚拟机为了优化性能,对 synchronized 引入了极其精妙的“锁升级”机制。今天,我们就通过深度分析对象头、CAS 竞争和 Monitor 机制,彻底讲透锁升级的原理。
1. 这篇文章要解决什么问题?
早期的 synchronized 只有 重量级锁 一种形式。每当一个线程请求锁,都会触发:
- 用户态与内核态的切换。
- 线程的挂起与唤醒(操作系统层面)。
这种操作极其耗时。但在实际业务中,科学家们发现:
- 很多时候,锁其实只会被 同一个线程多次访问。
- 即使有竞争,往往也是 交替执行,竞争通过简单的自旋就能解决。
锁升级的目的,就是为了根据实际的竞争激烈程度,逐步“按需加重”锁的开销。
2. 核心原理:对象头里的“秘密花园”
要理解锁升级,必须先看懂 Java 对象头(Object Header)。每个 Java 对象在内存中都带有一个“头”,其中的 Mark Word 是控制锁状态的核心。
Mark Word 的动态平衡
在 64 位 JVM 中,Mark Word 占 8 个字节(64 bit)。它是一个“变色龙”,在不同的锁状态下,位域的含义完全不同:
| 锁状态 | 25 bit | 31 bit | 1 bit | 4 bit | 1 bit (偏向位) | 2 bit (锁标志位) |
|---|---|---|---|---|---|---|
| 无锁 | 未使用 | hashCode | 0 | 分代年龄 | 0 | 01 |
| 偏向锁 | ThreadID (54bit) | Epoch (2bit) | 0 | 分代年龄 | 1 | 01 |
| 轻量级锁 | 指向栈中锁记录 (Lock Record) 的指针 | 00 | ||||
| 重量级锁 | 指向互斥量 (Monitor) 的指针 | 10 |
3. 流程/机制描述:一步步进阶的锁
第一阶段:偏向锁 (Biased Locking) —— 一个人的舞台
核心逻辑:如果锁总是被同一个线程获取,那么标记一下线程 ID 就行了,连 CAS 都不需要。
- 动作:当线程第一次访问同步块,Mark Word 会记录下当前线程 ID,偏向位置为 1。
- 进入:下次该线程再来,只需检查 ThreadID 是否匹配,匹配直接通过。
- 撤销:当有另一个线程尝试竞争时,偏向锁会被撤销,根据对象是否仍锁住决定升级。
第二阶段:轻量级锁 (Lightweight Locking) —— 彬彬有礼的竞争
核心逻辑:当出现了多个线程交替访问,但没有激烈竞争时,使用 CAS 替换对象头。
- 动作:线程在自己的栈帧中开辟空间(Lock Record),并将对象的 Mark Word 拷贝过去。
- CAS 争夺:线程尝试用 CAS 将对象头的 Mark Word 替换为指向自己栈中 Lock Record 的指针。
- 自旋:如果 CAS 失败,说明有轻微竞争,线程不会挂起,而是“原地踏步”(自旋)一会儿继续尝试。
第三阶段:重量级锁 (Heavyweight Locking) —— 实打实的冲突
核心逻辑:当自旋次数过多,或者多个线程同时激烈争夺时,锁变得极其沉重。
- 升级触发:轻量级锁 CAS 失败次数达到限度,或者同时有多个线程在自旋等待。
- Monitor 介入:锁膨胀为重量级锁,Mark Word 指向
ObjectMonitor对象。 - 挂起:除了持有锁的线程,其它线程全部进入
WaitSet或EntryList等待区,被操作系统挂起,进入阻塞状态。
4. 关键代码/示例
字节码维度的 synchronized
通过 javap -c 观察代码,你会发现同步块是由配对的指令控制的。
public class SyncExample {
public void syncBlock() {
synchronized (this) {
// 业务逻辑
}
}
}
对应的字节码摘要:
0: aload_0
1: dup
2: astore_1
3: monitorenter // 代表锁的开始
...
15: monitorexit // 代表正常退出
...
21: monitorexit // 代表异常退出(确保锁一定释放)
如何观察对象头?
在实际工作中,我们可以使用 JOL (Java Object Layout) 工具来实时观察 Mark Word 的位变化。
import org.openjdk.jol.info.ClassLayout;
/**
* 使用 JOL 观察对象头锁标志位
*/
public class JOLDemo {
public static void main(String[] args) {
Object obj = new Object();
// 1. 无锁状态
System.out.println("--- 无锁状态 ---");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
// 2. 这种情况下可能是偏向锁或轻量锁(取决于 JVM 启动参数)
System.out.println("--- 锁住状态 ---");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
}
注:由于 JVM 默认偏向锁有 4s 延迟,直接运行可能先看到轻量级锁。
5. 常见误区
误区 1:锁升级是可逆的
反驳:在 HotSpot 虚拟机中,锁升级通常是 单向不可逆 的(偏向锁 -> 轻量级锁 -> 重量级锁)。虽然在偏向锁撤销后可能重新进入偏向状态,但轻量锁一旦升级为重量锁,通常不会再自动“降级”回去。
误区 2:自旋锁就是轻量级锁
反驳:自旋(Spin)只是一种 手段,它发生在轻量级锁升级为重量级锁的过程中。轻量级锁本身是利用 CAS 替换指针,而当 CAS 失败时,才会开启自旋优化以期不挂起线程。
6. 实际工作中怎么用?
-
预估并发压力: 如果你的场景天生就是极高并发、大量争抢(如秒杀核心逻辑),
synchronized可能会迅速膨胀为重量级锁。此时可以考虑ReentrantLock提供的更丰富的 API。 -
JVM 调优建议:
- 偏向锁延迟:默认 4 秒延迟开启。如果你的应用一启动就有大量线程竞争,可以考虑通过
-XX:BiasedLockingStartupDelay=0来关闭延迟,或者彻底禁用偏向锁。 - 现代趋势:值得注意的是,JDK 15 之后已经默认禁用了偏向锁,因为维护偏向锁撤销的成本在现代多核架构下有时反而得不偿失。
- 偏向锁延迟:默认 4 秒延迟开启。如果你的应用一启动就有大量线程竞争,可以考虑通过
总结
synchronized 的设计哲学体现了 Java 性能优化的核心思想:平路加速,上坡减挡。在没有竞争时追求极致性能,在竞争激烈时保证结果正确。理解锁升级,是你通向 Java 并发架构师的必经之路。
相关推荐
专题
+ 收藏
+ 收藏
+ 收藏
+ 收藏
+ 收藏
+ 收藏
最新数据
相关文章
阿里云大模型服务平台百炼新人免费额度如何申请?申请与使用免费额度教程及常见问题解答
办公 AI 工具 OpenClaw 部署 Windows 系统一站式教程
Qwen3.6 正式发布!阿里云百炼同步开启“AI大模型节省计划”超值优惠
【新手零难度操作 】OpenClaw 2.6.4 安装误区规避与快速使用指南(包含最新版安装包)
OpenClaw 2.6.4 可视化部署 打造个人 AI 数字员工(包含最新版安装包)
【小白友好!】OpenClaw 2.6.4 本地 AI 智能体快速搭建教程(内有安装包)
零基础部署 OpenClaw v2.6.2,Windows 系统完整教程
【适合新手的】零基础部署 OpenClaw 自动化工具教程
开发者们的第一台自主进化的“爱马仕”来了
极简部署 OpenClaw 2.6.2 本地 AI 智能体快速启用(含最新版安装包)
AI精选
