跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava

Java synchronized 关键字详解:从字节码到对象头与锁升级

综述由AI生成Java synchronized 关键字底层基于 monitor 实现,涉及字节码指令 monitorenter/monitorexit。JVM 通过对象头 Mark Word 管理锁状态,支持无锁、偏向锁、轻量级锁和重量级锁四种状态。锁升级机制旨在减少性能开销,避免频繁用户态内核态切换。文章详细解析了锁的获取、释放流程及内存屏障作用。

LinuxPan发布于 2026/3/24更新于 2026/5/56 浏览
Java synchronized 关键字详解:从字节码到对象头与锁升级

synchronized 底层原理(总结版)

synchronized 底层使用的是 monitor,Monitor 被翻译为监视器,是由 JVM 提供,C++ 语言实现。

使用 javap -v xxx.class 反编译一段代码可以看到机器指令:

  • monitorenter:上锁开始的地方
  • monitorexit:解锁的地方
  • 其中被 monitorenter 和 monitorexit 包围住的指令就是上锁的代码
  • 第二个 monitorexit 是为了防止锁住的代码抛异常后不能及时释放锁

Monitor 主要就是跟这个对象产生关联,如下图:

文章配图

Monitor 内部具体的存储结构:

  • Owner:存储当前获取锁的线程,只能有一个线程可以获取
  • EntryList:关联没有抢到锁的线程,处于 Blocked 状态的线程
  • WaitSet:关联调用了 wait 方法的线程,处于 Waiting 状态的线程

具体的流程:

  • 进入 synchronized 代码块时,先让 lock(对象锁)关联 monitor,然后判断 Owner 是否有线程持有
  • 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
  • 如果有线程持有,则让当前线程进入 entryList 进行阻塞,如果 Owner 持有的线程已经释放了锁,在 EntryList 中的线程去竞争锁的持有权(非公平)
  • 如果代码块中调用了 wait() 方法,则会进去 WaitSet 中进行等待

synchronized 底层原理(详解版)

synchronized 的底层原理可以从三个层面来看:字节码层面、JVM 底层实现 和 硬件层面。我们逐层深入。

1. 字节码层面:monitorenter 和 monitorexit

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

  • 同步代码块: 对于 synchronized(object) { ... },编译器会在同步代码块的前后分别生成 monitorenter 和 monitorexit 指令。
public void method() {
    synchronized(obj) {
        // 同步代码块
        System.out.println("hello");
    }
}

编译后的字节码大致如下:

public void method(); Code:
  0: aload_0
  1: getfield #2 // 获取对象引用 obj
  4: dup
  5: astore_1
  6: monitorenter // 进入同步块,尝试获取锁
  7: getstatic #3 // 获取 System.out
 10: ldc #4 // 加载 "hello"
 12: invokevirtual #5 // 调用 println
 15: aload_1
 16: monitorexit // 正常退出同步块,释放锁
 17: goto 25
 20: astore_2
 21: aload_1
 22: monitorexit // 异常退出同步块,释放锁 (确保在异常情况下也能释放锁)
 23: aload_2
 24: athrow
 25: return

关键点:

  • 可以看到有两个 monitorexit 指令,第一个用于正常退出,第二个用于处理异常情况(隐藏在 finally 语义中),这确保了即使同步块内抛出异常,锁也能被正确释放。

  • 同步方法: 对于 synchronized 修饰的方法,方法常量池中会设置 ACC_SYNCHRONIZED 标志。

public synchronized void method() {
    // 方法体
}
  • 当方法调用时,调用指令(如 invokevirtual)会检查这个标志。如果设置了,执行线程会先尝试获取锁(对于实例方法是 this,对于静态方法是该类的 Class 对象),再执行方法体。方法执行完毕后,无论是正常返回还是异常抛出,都会自动释放锁。

小结:从字节码看,synchronized 的实现依赖于 monitorenter 和 monitorexit 这一对指令,或者方法的 ACC_SYNCHRONIZED 标志。

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 在不同状态下的结构:

(在 64 位 JVM 下,结构类似,只是空间更大。)

关键点:注意最后 2 位(lock),它标识了对象的锁状态。锁的升级过程就体现在这 2 位的变化上。

2.2 Monitor(管程/监视器锁)

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

一个 Monitor 由以下部分组成:

  • Owner:当前持有该 Monitor 的线程。初始为 null。
  • EntryList:处于 Blocked 状态的、等待锁的线程队列。当 Owner 释放锁时,JVM 会从 EntryList 中挑选一个线程来成为新的 Owner。
  • WaitSet:处于 Waiting 状态的、调用了 Object.wait() 方法的线程队列。这些线程在等待其他线程的通知(notify/notifyAll)。

工作流程:

  1. 当线程执行到 monitorenter 指令时,会尝试进入(enter)该对象的 Monitor。
  2. 如果 Monitor 的 Owner 为 null,则该线程成功成为 Owner,并将锁的计数器 +1。
  3. 如果该线程已经是 Owner(可重入锁),它再次进入,锁计数器再次 +1。
  4. 如果 Owner 是其他线程,则当前线程会进入 EntryList,进入 BLOCKED 状态,直到 Owner 线程释放锁。
  5. 当线程执行 monitorexit 指令时,锁计数器 -1。当计数器减到 0 时,线程释放 Monitor,不再担任 Owner。然后,EntryList 中的线程会开始竞争锁。
3. 锁的升级与优化

在 Java 6 之前,synchronized 是一个重量级锁,性能较差,因为它依赖于操作系统的 Mutex Lock(互斥锁),需要进行用户态到内核态的切换,耗时较长。

为了减少这种性能开销,Java 6 引入了锁升级机制。synchronized 的锁状态从低到高分为四种,升级路径是单向的:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

3.1 偏向锁
  • 目的:在没有竞争的情况下,消除整个同步操作。假设在大多数情况下,锁不仅不存在竞争,而且总是由同一线程多次获得。
  • 原理:
    • 当一个线程访问同步块时,会在对象头和栈帧中的锁记录里存储偏向的线程 ID。
    • 以后该线程再次进入和退出同步块时,不需要进行 CAS 操作来加锁和解锁,只需简单测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁。
    • 如果测试成功,表示线程已经获得了锁。
  • 撤销:一旦出现另一个线程来尝试竞争锁,偏向模式就宣告结束。持有偏向锁的线程会被挂起,JVM 会撤销偏向锁,然后升级为轻量级锁。

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

3.2 轻量级锁
  • 目的:在竞争不激烈('近交替执行')的情况下,避免直接使用重量级锁带来的性能消耗。
  • 加锁过程:
    1. 在当前线程的栈帧中创建一个名为 锁记录 的空间。
    2. 将对象头的 Mark Word 复制到锁记录中(称为 Displaced Mark Word)。
    3. 线程尝试使用 CAS 操作将对象头的 Mark Word 替换为指向锁记录的指针。
      • 如果成功,当前线程获得锁。并将对象 Mark Word 的最后 2 位设置为 00,表示轻量级锁状态。
      • 如果失败,表示存在竞争(另一个线程也修改了 Mark Word)。
  • 解锁过程:
    • 使用 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. 1. 字节码层面:monitorenter 和 monitorexit
  2. 2. JVM 底层实现:对象头与 Monitor
  3. 2.1 Java 对象头(Mark Word)
  4. 2.2 Monitor(管程/监视器锁)
  5. 3. 锁的升级与优化
  6. 3.1 偏向锁
  7. 3.2 轻量级锁
  8. 3.3 重量级锁
  9. 4. 硬件层面:内存屏障与 CAS
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Mac Intel 本地 LLM 部署实践:Ollama、Transformers 与 OpenVINO 对比
  • 归并排序与数组逆序对问题详解
  • MySQL 表基础语法:增删查与函数核心技巧
  • SimVascular 医学影像建模与血流仿真技术指南
  • HeartMuLa 音乐创作工具本地部署指南
  • 约瑟夫问题详解
  • 大模型技术进阶路线:从基础理论到生产环境优化
  • Python 调用高德地图 MCP 服务查询天气示例
  • DeepSeek 本地部署最简教程:基于 Ollama 的 GUI 交互方案
  • Linux 下 Tomcat 结合内网穿透实现公网访问
  • NLP 大模型应用于时间序列预测的五大方法综述
  • Spring Boot 集成 WebClient 调用第三方接口实战指南
  • C++ 类和对象基础概念详解
  • Java 网络编程:Socket 套接字基础与实现
  • 前端安全实践:防止 XSS、CSRF 与数据泄露
  • 自然语言处理在金融领域的应用与实战
  • 算法空间复杂度详解:概念与计算实例
  • llama.cpp 量化大模型部署与运行指南
  • 算法实战:Z 字形变换与外观数列详解
  • Windows 11 安装 JDK 25:下载、配置及验证步骤

相关免费在线工具

  • 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