【并发编程】 --- 从字节码指令的角度去理解synchronized关键字的原理

源码地址:https://github.com/nieandsun/concurrent-study.git


不知道大家有没有这样一种感觉,对于synchronized关键字我(1)会使用,(2)懂原理,但仍然还是想找到官方的证明,告诉自己这一切都是正确的,对的。 — 或许不止synchronized关键字,好多问题貌似都这样。。。


1 synchronized & 字节码指令

首先应该知道,我们写的代码最终都会被转化为一条条的字节码指令,运行在JVM上。

比如如下代码:

package com.nrsc.ch1.base.jmm.syn_study.deep;

public class SynDeepStudy {

    public void test1() {
        synchronized (this) {
            System.out.println("test1");
        }
    }

    public synchronized void test2() {
        System.out.println("test2");
    }

    public static synchronized void test3() {
        System.out.println("test3");
    }
}

我们可以先找到其编译后的.class文件 — SynDeepStudy.class,然后通过javap命令反汇编得到其字节码指令,比如我用下面的命令将其反汇编结果输出到一个txt文件里 :

javap -p -v -c SynDeepStudy.class > syn.txt

下图是方法test1()对应的字节码指令:
在这里插入图片描述
从上图我们至少可以看到两点:

  • (1)进入synchronized代码块之前会插入一个monitorenter指令,出去时会插入一条monitorexit
  • (2)如果synchronized代码块里发生异常时,也会走monitorexit指令 —》 即释放锁。

接下来从JDK官网看一看对这个monitorenter和monitorexit指令的描述。

2 字节码指令 — monitorenter

官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter

官网对monitorenter指令的描述如下:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

  • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
  • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
  • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

翻译如下:
每一个对象都会有一个monitor对象与之进行关联。 这个monitor对象只有在拥有所有者的情况下才会被锁定。 执行monitorenter指令的线程尝试获取锁对象对应的monitor所有权的过程如下:

  • (1) 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为 monitor的owner(所有者)。
  • (2) 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1。
  • (3) 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

联系上文《【并发编程】 — synchronized/ReentrantLock两大特性(可重入性和不可中断性)介绍》提到的锁的重入,这里再重新叙述一遍:

synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个
同步代码块,发现锁对象没有monitor就会创建一个monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程;recursions:会记录线程拥有锁的次数(重入的次数)。当一个线程拥有monitor后其他线程只能等待。


3 字节码指令 — monitorexit

官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorexit

官网对monitorexit指令的描述如下:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻译如下:

  • (1) 能执行monitorexit指令的线程一定是拥有当前锁对象的monitor所有权的线程。
  • (2)执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出
    monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个
    monitor的所有权。

结合1中的图可以看到:
monitorexit插入在同步代码块结束处和异常处。JVM会保证每个monitorenter必须有对应的monitorexit。


4 同步方法对应的字节码指令

接下来看一下1中代码里test2()和test3()方法对应的字节码指令:
在这里插入图片描述
从图中可以看到,无论是静态同步代码块,还是非静态同步代码块对应的字节码指令:

  • (1)都没有出现monitorentermonitorexit指令;
  • (2)在方法上都多了一个修饰符ACC_SYNCHRONIZED

这是怎么回事呢?查看官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10可知:

执行同步代码块会隐式地调用monitorenter和monitorexit指令 — 在执行同步方法前会调用monitorenter,在执行完同步方法后会调用monitorexit。


小结

通过本文前面4小结,可以得到如下结论:

使用synchronized关键字修饰的同步代码块或同步方法,在转为JVM运行的字节码指令时,会在前后插入monitorentor和monitorexit两个指令。每个作为锁的对象都会关联一个monitor对象(其实该monitor对象才是真正的锁对象),该对象内部有两个重要的成员变量:

  • owner会保存获得锁的线程
  • recursions会保存线程获得锁的次数(或者说可重入的次数)

当执行到monitorexit时,recursions会-1,当recursions减到0时这个线程就会释放锁;
当然如果同步代码块里出现了异常也会释放锁。


需要思考的问题

不知道你有没有这样一种感觉:

虽然从字节码指令的角度知道了synchronized关键字的原理,但是仍然会对它有很多疑惑,比如说:

  • (1)这个monitor对象到底是什么???
  • (2)为什么很多资料上都说monitor是重量级锁???
  • (3)相信大家都知道jdk1.6对synchronized关键字进行了优化
    • 优化中涉及到了锁对象对象头中的mark word,那这和monitor有啥关系???
    • monitorentor、monitorexit这两个字节码指令和mark word 以及monitor到底又是啥关系???

如果理不清这些问题,肯定你也会觉得synchronized关键字的了解还差点什么。。。


end

发布了225 篇原创文章 · 获赞 319 · 访问量 53万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/105226145
今日推荐