跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java synchronized 深入解析:从字节码指令到锁升级机制

综述由AI生成synchronized 是 Java 内置互斥锁,底层基于 Monitor 实现。本文从字节码指令 monitorenter/monitorexit 入手,解析 JVM 对象头 Mark Word 的结构变化,详细阐述了偏向锁、轻量级锁到重量级锁的升级路径及触发条件。同时结合内存屏障与 CAS 操作,说明了其如何保证原子性、可见性与有序性,帮助开发者深入理解并发安全机制。

数字游民发布于 2026/3/27更新于 2026/6/219 浏览
Java synchronized 深入解析:从字节码指令到锁升级机制

Java synchronized 深入解析:从字节码指令到锁升级机制

在 Java 并发编程中,synchronized 是最基础也是最常用的同步工具。很多人知道怎么用,但对底层的实现原理却知之甚少。今天我们从字节码、JVM 对象头以及锁升级机制三个层面,把 synchronized 的底层逻辑彻底讲清楚。

1. 字节码层面:monitorenter 和 monitorexit

当我们使用 synchronized 关键字时,无论是修饰代码块还是方法,编译器都会在生成的字节码中插入对应的指令。

同步代码块

对于 synchronized(object) { ... } 这种形式,编译器会在同步块的前后分别生成 monitorenter 和 monitorexit 指令。

public void method() {
    synchronized (obj) {
        System.out.println("hello");
    }
}

反编译后的字节码大致如下(简化展示):

0: aload_0          // 加载 this
1: getfield #2      // 获取 obj 引用
4: dup              // 复制引用
5: astore_1         // 存入局部变量表
6: monitorenter     // 尝试获取锁
7: getstatic #3     // 获取 System.out
...
15: aload_1         // 准备释放锁
16: monitorexit     // 正常退出同步块,释放锁
17: goto 25
20: astore_2        // 捕获异常
21: aload_1         // 再次准备释放锁
22: monitorexit     // 异常退出同步块,释放锁
23: aload_2
24: athrow
25: return

这里有个细节值得注意:你会看到有两个 monitorexit 指令。第一个用于正常流程退出,第二个隐藏在异常处理逻辑中。这是为了确保即使同步块内部抛出异常,锁也能被正确释放,避免死锁。

同步方法

对于 synchronized 修饰的方法,方法常量池中会设置 ACC_SYNCHRONIZED 标志。当调用该方法的指令(如 invokevirtual)执行时,虚拟机会检查这个标志。如果设置了,线程会先尝试获取锁(实例方法是 this,静态方法是类对象),再执行方法体。方法执行完毕,无论正常返回还是异常抛出,都会自动释放锁。

2. JVM 底层实现:对象头与 Monitor

monitorenter 和 monitorexit 指令背后的具体实现,是 JVM 的核心。其关键在于 Java 对象头 和 Monitor。

2.1 Java 对象头(Mark Word)

在 HotSpot 虚拟机中,每个 Java 对象在内存中的布局分为三部分:对象头、实例数据、对齐填充。其中,对象头 是理解锁的关键。

对象头包含两部分信息:

  1. Mark Word:存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等。它是实现锁的'主战场'。
  2. Klass Pointer:指向类元数据的指针。

为了在极小的空间内存储尽可能多的信息,Mark Word 被设计成一个非固定的动态数据结构。它会根据对象的状态复用自己的存储空间。在 32 位 JVM 下,Mark Word 的最后 2 位(lock)标识了对象的锁状态,锁的升级过程就体现在这 2 位的变化上。

2.2 Monitor(管程/监视器锁)

JVM 为每个对象都关联了一个内置的 Monitor(管程)。monitorenter 指令的本质就是尝试去获取这个对象对应的 Monitor。

一个 Monitor 主要由以下部分组成:

  • Owner:当前持有该 Monitor 的线程。初始为 null。
  • EntryList:处于 BLOCKED 状态的、等待锁的线程队列。
  • WaitSet:处于 WAITING 状态的、调用了 Object.wait() 方法的线程队列。

工作流程简述: 当线程执行到 monitorenter 指令时,会尝试进入该对象的 Monitor。如果 Owner 为 null,则成为 Owner;如果已经是 Owner(可重入锁),计数器 +1;如果是其他线程持有,则进入 EntryList 阻塞。执行 monitorexit 时,计数器 -1,减到 0 时释放 Monitor,EntryList 中的线程开始竞争。

3. 锁的升级与优化

在 Java 6 之前,synchronized 是一个重量级锁,性能较差,因为它依赖于操作系统的 Mutex Lock,涉及用户态到内核态的切换。为了减少开销,Java 6 引入了锁升级机制。锁状态从低到高分为四种,升级路径是单向的:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

3.1 偏向锁

  • 目的:在没有竞争的情况下,消除整个同步操作。假设大多数情况下锁总是由同一线程多次获得。
  • 原理:当一个线程访问同步块时,会在对象头和栈帧中的锁记录里存储偏向的线程 ID。以后该线程再次进入,只需测试 Mark Word 是否指向当前线程,无需 CAS 操作。
  • 撤销:一旦出现另一个线程尝试竞争锁,偏向模式结束。持有偏向锁的线程会被挂起,JVM 撤销偏向锁,升级为轻量级锁。

注意:在 Java 15 之后,偏向锁被标记为废弃并默认禁用,因为维护收益已不如从前,但理解其原理依然重要。

3.2 轻量级锁

  • 目的:在竞争不激烈('近交替执行')的情况下,避免直接使用重量级锁带来的性能消耗。
  • 加锁过程:
    1. 在当前线程的栈帧中创建一个名为 锁记录 的空间。
    2. 将对象头的 Mark Word 复制到锁记录中(Displaced Mark Word)。
    3. 线程尝试使用 CAS 操作将对象头的 Mark Word 替换为指向锁记录的指针。
      • 成功:获得锁,Mark Word 最后 2 位设为 00。
      • 失败:存在竞争,需自旋或升级。
  • 解锁过程:使用 CAS 操作将 Displaced Mark Word 替换回对象头。如果失败,说明锁已升级,需唤醒被挂起的线程。

3.3 重量级锁

  • 触发条件:当轻量级锁竞争失败后,会自旋尝试获取锁一定次数。如果自旋后依然失败,锁就会膨胀为重量级锁。
  • 特点:此时 Mark Word 中存储的是指向重量级锁(Monitor)的指针。等待锁的线程都会进入 EntryList,进入 BLOCKED 状态,依赖操作系统底层的 Mutex Lock,成本最高。

4. 硬件层面:内存屏障与 CAS

synchronized 的语义保证了原子性、可见性和有序性。

  • 可见性与有序性:通过插入 内存屏障 实现。在同步块开始时加 Load Barrier,结束时加 Store Barrier,强制刷新工作内存到主内存,禁止指令重排序。
  • 原子性:对于简单的 monitorenter/monitorexit,由 Monitor 保证。对于锁升级过程中的状态变更,则是通过 CAS 操作实现的。CAS 是一条 CPU 原子指令(cmpxchg),保证了'比较 - 交换'操作的原子性。

理解这些底层机制,能帮助我们在实际开发中更合理地选择同步策略,写出更高效、安全的并发代码。

目录

  1. Java synchronized 深入解析:从字节码指令到锁升级机制
  2. 1. 字节码层面:monitorenter 和 monitorexit
  3. 同步代码块
  4. 同步方法
  5. 2. JVM 底层实现:对象头与 Monitor
  6. 2.1 Java 对象头(Mark Word)
  7. 2.2 Monitor(管程/监视器锁)
  8. 3. 锁的升级与优化
  9. 3.1 偏向锁
  10. 3.2 轻量级锁
  11. 3.3 重量级锁
  12. 4. 硬件层面:内存屏障与 CAS
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 环境搭建与 Hello World 入门实战
  • 数据结构:双向链表原理与 C 语言实现
  • 低代码治理实践:构建 Power Platform 中心化管控体系
  • Rust 异步微服务架构最佳实践与反模式
  • 网络安全攻防对抗:身份安全是核心防线
  • Arrow 游戏叙事工具:三大实战场景与可视化创作
  • ES6 新特性实战:进制表示、Symbol 与类继承
  • GitHub Copilot 学生认证指南:两年免费 Pro 权益申请流程
  • Linux 进程间通信:匿名管道原理与实现
  • C++ 泛型编程与模板技术详解
  • jQuery 从入门到实战全解:前端高效开发核心
  • Whisper v0.2 本地语音转文字工具安装与使用指南
  • 双指针算法实战:移动零与复写零详解
  • SSM 框架文件上传功能实战:从前端到后端完整流程
  • 医疗大模型:数据与知识双轮驱动的医学推理与临床决策支持
  • ComfyUI 工作流适配 Z-Image:可视化节点让 AI 绘画更高效
  • 基于 DeepFace 与 OpenCV 的实时情绪分析系统
  • MySQL 内置函数实战:日期、字符串与数学运算详解
  • 高频 SQL 50 题:聚合函数实战
  • KingbaseES 数据库智能 SQL 防护机制与实战配置

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online