一、Javap反汇编
1.1 反汇编
通过javap反汇编学习synchronized的原理
我们编写一个简单的synchronized代码,如下:
public class Demo05_monitor {
private static Object obj = new Object();
public static void main(String[] args) {
synchronized (obj) {
System.out.println("1");
}
}
public synchronized void test() {
System.out.println("a");
}
}
我们要看synchronized的原理,但是synchronized是一个关键字,看不到源码。
我们可以将class文件进行反汇编。
JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。
在DOS命令行输入:
javap -p -v -c C:\Users\13666\IdeaProjects\HeiMa\Synchronized\target\classes\com\itheima\demo04_synchronized_monitor\Demo05.class
反汇编后的效果如下:
1.2 monitorenter
首先我们来看一下JVM规范中对于monitorenter的描述:Oracle官方解释
每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。
当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。
其过程如下:
1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。
monitorenter小结:
synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,
monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待。
1.3 monitorexit
首先我们来看一下JVM规范中对于monitorexit的描述:Oracle-monitorexit
1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,
此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权
总结:
monitorexit释放锁。
monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。
面试题synchroznied出现异常会释放锁吗?
会释放锁
1.4 同步方法
可以看到同步方法在反汇编后,会增加ACC_SYNCHRONIZED 修饰。会隐式调用monitorenter和monitorexit。
在执行同步方法前会调用monitorenter,在执行完同步方法后会调用monitorexit。
总结:
通过javap反汇编我们看到synchronized使用编程了monitorentor和monitorexit两个指令。
每个锁对象都会关联一个monitor(监视器,它才是真正的锁对象),它内部有两个重要的成员变量owner会保存获得锁的线程,
recursions会保存线程获得锁的次数,当执行到monitorexit时,recursions会-1,当计数器减到0时这个线程就会释放锁