复生娜免安装正式中文版
22.3G · 2025-09-16
本文深入剖析 synchronized
的底层实现,从字节码角度解析 monitorenter
和 monitorexit
指令,结合对象头中的 Mark Word、JVM Monitor 实现机制,揭示锁的本质运行逻辑,并结合代码示例帮助开发者掌握 synchronized 的内部工作原理。
在 Java 并发编程中,synchronized
是最常用、最易理解的同步机制之一。它提供了原子性、可见性和有序性保证,但开发者常常只停留在“给方法或代码块加锁”的层面。
实际上,synchronized
的底层实现远比表面看到的关键字复杂得多,它依赖于 字节码指令 monitorenter
与 monitorexit
,并与 对象头(Object Header)和 JVM 内置的 Monitor 机制紧密结合。
本文将从字节码层面展开,带你走进 synchronized 的“黑盒子”,看清它的内部结构与运行逻辑。
synchronized
主要有三种常见用法:
public synchronized void test() {
// 临界区
}
public static synchronized void testStatic() {
// 临界区
}
public void testBlock() {
synchronized (this) {
// 临界区
}
}
从表面看,这三种方式差别不大,实则在字节码层面都依赖 monitorenter
和 monitorexit
指令来完成加锁与释放锁。
我们以 synchronized 代码块 为例,编写一段简单代码:
public class SyncDemo {
public void method() {
synchronized (this) {
System.out.println("Hello synchronized");
}
}
}
使用 javac
编译后,再用 javap -v SyncDemo.class
查看字节码。核心部分如下:
public void method();
descriptor: ()V
flags: ACC_PUBLIC
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Hello synchronized
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
可以看到:
monitorenter
:进入同步代码块时加锁。monitorexit
:正常退出时释放锁。monitorexit
:异常退出时释放锁(保证异常情况下不会发生死锁)。? 这正是 Java 语言规范中规定的:每个 monitorenter 必须对应至少一个 monitorexit。
因此,synchronized
并不是编译器的“魔法”,而是依赖 JVM 的字节码指令实现的。
要理解 monitorenter
的运行机制,需要先掌握 对象头(Object Header) 与 Monitor 的概念。
在 HotSpot 虚拟机中,每个 Java 对象在内存布局上分为三部分:
对象头中包含 Mark Word,它是实现锁的关键数据结构。
Mark Word 内容示例:
位数 | 内容 |
---|---|
25 | 哈希码(HashCode) |
31 | GC 分代年龄 |
2 | 锁标志位(01、00、10 等) |
1 | 是否偏向锁标志 |
当一个对象被 synchronized
加锁时,JVM 会修改其 Mark Word 来指向对应的 Monitor。
Monitor 可以理解为一个同步工具,它本质上依赖于 操作系统的互斥锁(Mutex Lock) 。在 HotSpot 中,Monitor 是由 ObjectMonitor C++ 类实现的。
ObjectMonitor 中的重要字段:
wait()
进入等待状态的线程集合。当执行 monitorenter
时:
我们结合执行路径来理解:
在前面的字节码示例中,我们发现:有两个 monitorexit 指令。
原因在于 Java 需要确保:
否则,如果在异常情况下未释放锁,就可能造成死锁。
前面我们看的是代码块的字节码。那么如果是 synchronized 方法 呢?
public synchronized void test() {
System.out.println("sync method");
}
反编译字节码:
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
0: getstatic #2
3: ldc #3
5: invokevirtual #4
8: return
可以看到,这里并没有显式的 monitorenter
与 monitorexit
。
原因: JVM 在方法表中使用 ACC_SYNCHRONIZED
标志来表示该方法是同步的,方法执行时会自动加锁和释放锁。
ReentrantLock
等显式锁与 synchronized
的本质区别在于:
synchronized
是 JVM 层面实现的,依赖 字节码指令和对象头。Lock
是 API 层面的实现,依赖 AQS(AbstractQueuedSynchronizer) 等框架。但两者底层最终都会依赖 操作系统的互斥机制。
本文从三个层面剖析了 synchronized
的底层实现:
monitorenter
与 monitorexit
是核心指令,保证进入和退出时正确加锁/解锁。synchronized
并非“简单关键字”,而是 JVM 与操作系统协作的结果。理解它的底层机制,有助于我们写出更高效、更安全的并发代码。