在上一次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塊給合並為一個較大的同步塊,這樣做的好處在於線程在執行這些代碼時,就無需頻繁申請與釋放鎖了,從而達到申請與釋放鎖一次,就可以執行完全部的同步代碼塊,從而提升了性能。