java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java synchronized关键字

Java synchronized关键字从字节码到对象头与锁升级(最新整理)

作者:程序员三明治

susersynchronized底层原理详解,涉及Monitor、MarkWord、偏向锁与轻量级锁等机制,synchronized底层原理涉及Monitor实现、MarkWord结构、偏向锁及轻量级锁机制,深入剖析JVM层面与硬件层面实现细节

synchronized底层原理(总结版)

synchronize底层使用的是minitor,Monitor 被翻译为监视器,是由jvm提供,c++语言实现。

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

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

Monitor内部具体的存储结构:

具体的流程:

参考回答:

synchronized底层原理(详解版)

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

1. 字节码层面:monitorenter 和 monitorexit

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

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` 语义中),这确保了即使同步块内抛出异常,锁也能被正确释放。
public synchronized void method() {
    // 方法体
}
- 当方法调用时,调用指令(如 `invokevirtual`)会检查这个标志。如果设置了,执行线程会先尝试获取锁(对于实例方法是 `this`,对于静态方法是该类的 `Class` 对象),再执行方法体。方法执行完毕后,无论是正常返回还是异常抛出,都会自动释放锁。

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

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

monitorentermonitorexit 指令背后的具体实现,是 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 由以下部分组成:

工作流程

  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 偏向锁

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

3.2 轻量级锁

3.3 重量级锁

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

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

JVM锁升级是什么?

Monitor实现的锁属于重量级锁,你了解过锁升级吗?

对象的内存结构

MarkWord

我们可以通过lock的标识,来判断是哪一种锁的等级

再说Monitor重量级锁

每个对象的markword都可以设置monoitor的指针,让对象与monitor产生关联

轻量级锁

**加锁的流程 **

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

2.通过CAS指令将Lock Record的地址存储在对象头的mark word中(数据进行交换),如果对象处于无锁状态代表该线程获得了轻量级锁。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。

4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

解锁过程

1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。

2.如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。

3.如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word 恢复成为无锁状态。如果失败则膨胀为重量级锁。

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有.

**加锁的流程 **

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

2.通过CAS指令将Lock Record的线程id存储在对象头的mark word中,同时也设置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了偏向锁

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作,性能相对轻量级锁更好一些

解锁流程参考轻量级锁

到此这篇关于Java synchronized关键字从字节码到对象头与锁升级(最新整理)的文章就介绍到这了,更多相关Java synchronized关键字内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文