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语义中),这确保了即使同步块内抛出异常,锁也能被正确释放。 - 被
monitorenter和monitorexit包围住的指令就是上锁的代码范围。
- 同步方法:
对于
synchronized修饰的方法,方法常量池中会设置ACC_SYNCHRONIZED标志。当方法调用时,调用指令(如invokevirtual)会检查这个标志。如果设置了,执行线程会先尝试获取锁(对于实例方法是this,对于静态方法是该类的Class对象),再执行方法体。方法执行完毕后,无论是正常返回还是异常抛出,都会自动释放锁。
2. JVM 底层实现:对象头与 Monitor
monitorenter 和 monitorexit 指令背后的具体实现,是 JVM 的核心。其关键在于 Java 对象头 和 Monitor。
2.1 Java 对象头(Mark Word)
在 HotSpot 虚拟机中,每个 Java 对象在内存中存储的布局分为三部分:对象头、实例数据、对齐填充。
其中,对象头 是理解锁的关键。它包含两部分信息:
- Mark Word:存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等。它是实现锁的'主战场'。
- :对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。




