Java精通并发-锁粗化与锁消除技术实例演示与分析

在上一次https://www.cnblogs.com/webor2006/p/11446473.html中对锁的升级进行了一个比较详细的理论化的学习,先回忆一下:

 

编译器对于锁的优化措施: 

锁消除技术:

接下来则会通过实例来分析一下JIT编译器优化的一些方式,先来看第一个例子:

 

很简单的程序,然后反编译看一下它在字节码的表现:

 

 接下来则来修改一下程序:

其实反编译的字节码的锁还是会有的:

但是很明显这段同步的意义就不大了,因为每个线程在访问这个方法时的局部变量肯定都是不一样的,不同的对象锁也不一样,那何来的同步,所以其实JIT在程序运行时是比较智能的,JIT编译器(Just In Time编译器)可以在动态编译同步代码时,使用一种叫做逃逸分析的技术,来通过该项技术判别程序中所使用的锁对象是否只被一个线程所使用,而没有散布到其他线程当中;如果情况就是这样的话,那么JIT编译器在编译这个同步代码时就不会生成synchronized关键字标识的锁的申请和释放机器码,从而消除了锁的使用流程。

锁粗化:

好,接下来看另外一个例子:

 

根据上面的理论,很显然在运行是JIT是不会给代码上锁的,因为此object声明的是方法的局部变量,木啥意义,那如果将它改为成员变量呢?

 

可见这个方法块中多次给代码上了锁,下面看一下它在字节码上的表现:

  public void method();
    Code:
       0: aload_0
       1: getfield      #3                  // Field object:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #5                  // String hello world
      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit
      23: aload_2
      24: athrow
      25: aload_0
      26: getfield      #3                  // Field object:Ljava/lang/Object;
      29: dup
      30: astore_1
      31: monitorenter
      32: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      35: ldc           #7                  // String welcome
      37: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: aload_1
      41: monitorexit
      42: goto          50
      45: astore_3
      46: aload_1
      47: monitorexit
      48: aload_3
      49: athrow
      50: aload_0
      51: getfield      #3                  // Field object:Ljava/lang/Object;
      54: dup
      55: astore_1
      56: monitorenter
      57: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      60: ldc           #8                  // String person
      62: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      65: aload_1
      66: monitorexit
      67: goto          77
      70: astore        4
      72: aload_1
      73: monitorexit
      74: aload         4
      76: athrow
      77: return
    Exception table:
       from    to  target type
           7    17    20   any
          20    23    20   any
          32    42    45   any
          45    48    45   any
          57    67    70   any
          70    74    70   any

每一个synchronized块都对应一个monitorenter和两个monitorexit,其实JIT编译器在执行动态编译时会对上面代码进行优化:若发现前后相邻的synchronized块使用的是同一个锁对象,那么它就会把这几个synchronized块给合并为一个较大的同步块,这样做的好处在于线程在执行这些代码时,就无需频繁申请与释放锁了,从而达到申请与释放锁一次,就可以执行完全部的同步代码块,从而提升了性能。

posted on 2019-09-02 20:09  cexo  阅读(1402)  评论(0编辑  收藏  举报

导航