前面 3 篇文章講了 synchronized 的同步方法和同步程式碼塊兩種用法,還有鎖例項物件和鎖 Class 物件兩種鎖機制。今天我們來看看同步方法和同步程式碼塊的實現原理。
我們把前 3 篇有涉及到的 synchronized 方法全寫在一起,如下面所示。
public class SynchronizedPrincipleTest {
public void testNoSynchronized() {
System.out.println("hello testNoSynchronized");
}
public synchronized void testSynchronizedMethod() {
System.out.println("hello testSynchronizedMethod");
}
public static synchronized void testSynchronizedStatic() {
System.out.println("hello testSynchronizedStatic");
}
public void testSynchronizedCodethis() {
synchronized (this) {
System.out.println("hello testSynchronizedCode");
}
}
private Object lock = new Object();
public void testSynchronizedCodeObject() {
synchronized (lock) {
System.out.println("hello testSynchronizedCodeObject");
}
}
public void testSynchronizedCodeClass() {
synchronized (SynchronizedPrincipleTest.class) {
System.out.println("hello testSynchronizedCodeClass");
}
}
}
編寫好程式碼之後,我們通過 javac
命令編譯程式碼,使用 javap
命令反編譯出彙編程式碼出來。命令如下所示。
javac SynchronizedPrincipleTest.java
javap -v SynchronizedCodeTest.class
得出我們要彙編程式碼。
Classfile /D:/Workspace/finance/test/thread/src/main/java/com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.class
Last modified Apr 26, 2020; size 1363 bytes
MD5 checksum a03ec0b152580bb465b1defe7965a60d
Compiled from "SynchronizedPrincipleTest.java"
public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#31 // java/lang/Object."<init>":()V
#2 = Class #32 // java/lang/Object
#3 = Fieldref #11.#33 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.lock:Ljava/lang/Object;
#4 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #36 // hello testNoSynchronized
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = String #39 // hello testSynchronizedMethod
#8 = String #40 // hello testSynchronizedStatic
#9 = String #41 // hello testSynchronizedCode
#10 = String #42 // hello testSynchronizedCodeObject
#11 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#12 = String #44 // hello testSynchronizedCodeClass
#13 = Utf8 lock
#14 = Utf8 Ljava/lang/Object;
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 testNoSynchronized
#20 = Utf8 testSynchronizedMethod
#21 = Utf8 testSynchronizedStatic
#22 = Utf8 testSynchronizedCodethis
#23 = Utf8 StackMapTable
#24 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#25 = Class #32 // java/lang/Object
#26 = Class #45 // java/lang/Throwable
#27 = Utf8 testSynchronizedCodeObject
#28 = Utf8 testSynchronizedCodeClass
#29 = Utf8 SourceFile
#30 = Utf8 SynchronizedPrincipleTest.java
#31 = NameAndType #15:#16 // "<init>":()V
#32 = Utf8 java/lang/Object
#33 = NameAndType #13:#14 // lock:Ljava/lang/Object;
#34 = Class #46 // java/lang/System
#35 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#36 = Utf8 hello testNoSynchronized
#37 = Class #49 // java/io/PrintStream
#38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V
#39 = Utf8 hello testSynchronizedMethod
#40 = Utf8 hello testSynchronizedStatic
#41 = Utf8 hello testSynchronizedCode
#42 = Utf8 hello testSynchronizedCodeObject
#43 = Utf8 com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#44 = Utf8 hello testSynchronizedCodeClass
#45 = Utf8 java/lang/Throwable
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/io/PrintStream
#50 = Utf8 println
#51 = Utf8 (Ljava/lang/String;)V
{
public com.liebrother.study.synchronizeds.SynchronizedPrincipleTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field lock:Ljava/lang/Object;
15: return
LineNumberTable:
line 7: 0
line 27: 4
/** 無 synchronized 修飾的程式碼 */
public void testNoSynchronized();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String hello testNoSynchronized
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8
/** synchronized 修飾的例項方法 */
public synchronized void testSynchronizedMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED /** 方法標識多了一個 ACC_SYNCHRONIZED */
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String hello testSynchronizedMethod
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8
/** synchronized 修飾的靜態方法 */
public static synchronized void testSynchronizedStatic();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED /** 方法標識多了 ACC_STATIC 和 ACC_SYNCHRONIZED */
Code:
stack=2, locals=0, args_size=0
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String hello testSynchronizedStatic
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 18: 0
line 19: 8
/** synchronized 修飾的 this 程式碼塊 */
public void testSynchronizedCodethis();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter /** 通過 monitorenter 命令進入監視器鎖 */
4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #9 // String hello testSynchronizedCode
9: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
14: goto 22
17: astore_2
18: aload_1
19: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 22: 0
line 23: 4
line 24: 12
line 25: 22
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
/** synchronized 修飾的 object 程式碼塊 */
public void testSynchronizedCodeObject();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter /** 通過 monitorenter 命令進入監視器鎖 */
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #10 // String hello testSynchronizedCodeObject
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
17: goto 25
20: astore_2
21: aload_1
22: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
LineNumberTable:
line 29: 0
line 30: 7
line 31: 15
line 32: 25
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 20
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
/** synchronized 修飾的 xxx.Class 程式碼塊 */
public void testSynchronizedCodeClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #11 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
2: dup
3: astore_1
4: monitorenter /** 通過 monitorenter 命令進入監視器鎖 */
5: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #12 // String hello testSynchronizedCodeClass
10: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
15: goto 23
18: astore_2
19: aload_1
20: monitorexit /** 通過 monitorexit 命令退出監視器鎖 */
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
LineNumberTable:
line 35: 0
line 36: 5
line 37: 13
line 38: 23
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 18
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "SynchronizedPrincipleTest.java"
這段程式碼有點多,加了些註釋方便大家看,這裡我抽一些重要的點講一下。
- 我們可以看到同步方法和同步程式碼塊的同步實現不太一樣。
同步方法的實現是在方法標識 flags 中加了 ACC_SYNCHRONIZED 標識,是一種隱式實現,具體是 JVM 在執行方法的時候,檢查是否有 ACC_SYNCHRONIZED 同步標識,有的話會等待獲取監控器 monitor,然後在方法執行結束時釋放監控器 monitor。
同步程式碼塊的實現是在加同步程式碼塊前加上 monitorenter
指令,在同步程式碼塊後加上 monitorexit
指令,每個物件都有一個 monitor 監視器,當 monitor 被某執行緒佔用了,該執行緒就鎖定了該 monitor。每個 monitor 都維護一個自己的計數器,當執行 monitorenter 時,該計數器 +1,當執行 monitorexit 時候釋放鎖,計數器變為 0。其他執行緒才可以嘗試獲得 monitor,對共享資源進行操作。
-
同步例項方法
testSynchronizedMethod()
和同步靜態方法testSynchronizedStatic()
差別只是在於 flags 有沒有ACC_STATIC
標識,其實鎖例項物件還是鎖 Class 物件,也是 JVM 底層實現根據這個標識去做判斷,對我們來說是透明的。 -
同步程式碼塊鎖什麼物件
this
VSobject
VSxxx.class
,在這個彙編程式碼可以看出來的。
鎖 this
的程式碼如下。在進入 monitor 監聽器前,先獲取 this 物件,也就是進入 this 物件的 monitor 鎖。
0: aload_0 /** 載入當前 this 物件 */
1: dup /** 將 this 物件壓入棧頂 */
2: astore_1 /** 從棧頂取出 this 物件 */
3: monitorenter /** 獲取 this 物件的 monitor 鎖 */
鎖 object
的程式碼如下。在進入 monitor 監聽器前,先獲取 lock 物件,也就是進入 lock 物件的 monitor 鎖。
0: aload_0 /** 載入當前 this 物件 */
1: getfield #3 /** 獲取 this 物件的例項變數 lock */ // Field lock:Ljava/lang/Object;
4: dup /** 將例項變數 lock 壓入棧頂 */
5: astore_1 /** 從棧頂取出 lock 物件 */
6: monitorenter /** 獲取 lock 物件的 monitor 鎖 */
鎖 xxx.class
的程式碼如下。在進入 monitor 監聽器前,先獲取 Class 物件,也就是進入 Class 物件的 monitor 鎖。
0: ldc #11 /** 從常量池中獲取 SynchronizedPrincipleTest 類物件 */ // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
2: dup /** 將 Class 物件壓入棧頂 */
3: astore_1 /** 從棧頂取出 Class 物件 */
4: monitorenter /** 獲取 Class 物件的 monitor 鎖 */
今天從 Java 的彙編程式碼來分析同步方法和同步程式碼塊的底層實現,其實這塊還不算是真正的底層實現,只是站在 Java 層面上來說,這已經是最底層了。站在 JVM 這是最高層,接下來會從 JVM 角度來分析為什麼同步方法加上 ACC_SYNCHRONIZED
和 同步程式碼塊加上 monitorenter
& monitorexit
就可以實現多執行緒同步?
悄悄打個預防針,接下來的文章會有些晦澀難懂,但是我覺得很有必要弄懂它,弄懂了最底層原理,那麼多執行緒就不怕了,弄懂了,後面會給大家講的 AQS
就很容易懂,它是把 JVM 底層的實現搬到 Java 源庫。
原創不易,大家多點個贊,非常感謝!
推薦閱讀
:
寫了那麼多年 Java 程式碼,終於 debug 到 JVM 了
瞭解Java執行緒優先順序,更要知道對應作業系統的優先順序,不然會踩坑
後臺回覆『設計模式』可以獲取《一故事一設計模式》電子書
覺得文章有用幫忙轉發&點贊,多謝朋友們!