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块给合并为一个较大的同步块,这样做的好处在于线程在执行这些代码时,就无需频繁申请与释放锁了,从而达到申请与释放锁一次,就可以执行完全部的同步代码块,从而提升了性能。