JVM字节码分析--Synchronized关键字底层实现
一、代码块级别的 synchronized
一个代码块加synchronized关键字的例子:
private Object lock = new Object();
public void test() {
synchronized (lock) {
foo();
}
}
public void foo() { }
字节码反编译:
public void test();
Code:
0: aload_0
1: getfield #3 // private Object lock = new Object();
4: dup
5: astore_1
6: monitorenter
7: aload_0
8: invokevirtual #4 // Method foo:()V
11: aload_1
12: monitorexit
13: goto 21
16: astore_2
17: aload_1
18: monitorexit
19: aload_2
20: athrow
21: return
Exception table:
from to target type
7 13 16 any
16 19 16 any
代码块的同步是通过 monitorenter 和 monitorexit 两个指令来实现 synchronized 关键字的。上面的字节码中
0 ~ 5:将 lock 对象入栈,使用 dup 指令复制栈顶元素,并将它存入局部变量表位置 1 的地方,现在栈上还剩下一个 lock 对象
6:以栈顶元素 lock 做为锁,使用 monitorenter 开始同步
7 ~ 8:调用 foo() 方法
11 ~ 12:将 lock 对象入栈,调用 monitorexit 释放锁
16 ~ 20:执行异常处理,字节码会自动加上这段,因为编译器必须保证,无论同步代码块中的代码以何种方式结束(正常 return 或者异常退出),代码中每次调用 monitorenter 必须执行对应的 monitorexit 指令。为了保证这一点,编译器会自动生成一个异常处理器,这个异常处理器的目的就是为了同步代码块抛出异常时能执行 monitorexit。这也是字节码中,只有一个 monitorenter 却有两个 monitorexit 的原因。
总而言之,调用monitorenter指令后,先将lock出栈(解锁)。调用monitoexit指令退出后,再将lock入栈(上锁),如遇到异常则强制monitorexit退出。
二、方法级别的synchronized
方法级别的同步由常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现。
synchronized public void test() {
}
字节码反编译:
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
JVM 不会使用特殊的字节码来调用同步方法,当 JVM 解析方法的符号引用时,它会判断方法是不是同步的(检查方法 ACC_SYNCHRONIZED 是否被设置)。如果是,执行线程会先尝试获取锁。如果是实例方法,JVM 会尝试获取实例对象的锁,如果是类方法,JVM 会尝试获取类锁。在同步方法完成以后,不管是正常返回还是异常返回,都会释放锁。