在上一次中对锁的升级进行了一个比较详细的理论化的学习,先回忆一下:

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


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

锁消除技术:

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

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


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

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


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

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

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

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

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

锁粗化:

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

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


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

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


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


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