bilibili-Java併發學習筆記3 synchronized
基於 java 1.8.0
P11_synchronized關鍵字原理詳解
P12_透過字節碼理解synchronized關鍵字
- 對當前類實例上鎖
package new_package.thread.p12;
public class SynchronizedTest2 {
public synchronized void hello() {
int i = 18;
}
public void world() {
synchronized (this) {
int i = 19;
}
}
}
- 對當前class類上鎖
package new_package.thread.p12;
public class SynchronizedTest4 {
public static synchronized void hello() {
String sync = "static";
}
public void world() {
synchronized (SynchronizedTest4.class) {
String sync = "xxx";
}
}
}
- 對類中變量加鎖
package new_package.thread.p12;
public class SynchronizedTest6 {
Object obj = new Object();
public void hello() {
synchronized (obj) {
String xxx = "Object";
}
}
public void world() {
synchronized (obj) {
String str2 = "world";
throw new RuntimeException("my exception");
}
}
}
- javap -v new_package.thread.p12.SynchronizedTest2
{
public synchronized void hello();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=2, args_size=1
0: bipush 18
2: istore_1
3: return
LineNumberTable:
line 6: 0
line 7: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lnew_package/thread/p12/SynchronizedTest2;
3 1 1 i I
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: bipush 19
6: istore_2
7: aload_1
8: monitorexit
9: goto 17
12: astore_3
13: aload_1
14: monitorexit
15: aload_3
16: athrow
17: return
Exception table:
from to target type
4 9 12 any
12 15 12 any
LineNumberTable:
line 10: 0
line 11: 4
line 12: 7
line 13: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lnew_package/thread/p12/SynchronizedTest2;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class new_package/thread/p12/SynchronizedTest2, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
解析1 :
- 對於 synchronized 關鍵字修飾方法來說,並沒有 monitorenter 和 monitorexit 指令,而是出現了一個
ACC_SYNCHRONIZED
標識 - JVM 使用
ACC_SYNCHRONIZED
標識來區分一個方法是否爲同步方法
;如果是,執行線程會先持有方法所在對象的Monitor 對象
,然後再去執行方法體,線程執行完後(正常執行完返回,發生異常退出方法)釋放Monitor 對象
; - 線程持有
Monitor 對象
期間,其他線程將無法再獲取當前對象的Monitor 對象
;
- javap -v new_package.thread.p12.SynchronizedTest4
{
public new_package.thread.p12.SynchronizedTest4();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lnew_package/thread/p12/SynchronizedTest4;
public static synchronized void hello();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=1, args_size=0
0: ldc #2 // String static
2: astore_0
3: return
LineNumberTable:
line 5: 0
line 6: 3
LocalVariableTable:
Start Length Slot Name Signature
3 1 0 sync Ljava/lang/String;
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: ldc #3 // class new_package/thread/p12/SynchronizedTest4
2: dup
3: astore_1
4: monitorenter
5: ldc #4 // String xxx
7: astore_2
8: aload_1
9: monitorexit
10: goto 18
13: astore_3
14: aload_1
15: monitorexit
16: aload_3
17: athrow
18: return
Exception table:
from to target type
5 10 13 any
13 16 13 any
LineNumberTable:
line 9: 0
line 10: 5
line 11: 8
line 12: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lnew_package/thread/p12/SynchronizedTest4;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class new_package/thread/p12/SynchronizedTest4, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
解析 2 :
- 靜態同步方法,同時使用 ACC_STATIC, ACC_SYNCHRONIZED 兩個標識
- 線程執行時,首先獲取當前
Class 類對象
(注意區別於實例對象)的 Monitor 對象,…
- javap -v new_package.thread.p12.SynchronizedTest6
{
java.lang.Object obj;
descriptor: Ljava/lang/Object;
flags:
public new_package.thread.p12.SynchronizedTest6();
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 obj:Ljava/lang/Object;
15: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lnew_package/thread/p12/SynchronizedTest6;
public void hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: ldc #4 // String Object
9: astore_2
10: aload_1
11: monitorexit
12: goto 20
15: astore_3
16: aload_1
17: monitorexit
18: aload_3
19: athrow
20: return
Exception table:
from to target type
7 12 15 any
15 18 15 any
LineNumberTable:
line 8: 0
line 9: 7
line 10: 10
line 11: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this Lnew_package/thread/p12/SynchronizedTest6;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 15
locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: ldc #5 // String world
9: astore_2
10: new #6 // class java/lang/RuntimeException
13: dup
14: ldc #7 // String my exception
16: invokespecial #8 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
19: athrow
20: astore_3
21: aload_1
22: monitorexit
23: aload_3
24: athrow
Exception table:
from to target type
7 23 20 any
LineNumberTable:
line 14: 0
line 15: 7
line 16: 10
line 17: 20
LocalVariableTable:
Start Length Slot Name Signature
10 10 2 str2 Ljava/lang/String;
0 25 0 this Lnew_package/thread/p12/SynchronizedTest6;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 20
locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
}
解析3 :
- synchronized 代碼塊,字節碼層次上是通過 monitorenter 和 monitorexit 指令來實現
獲取鎖
和釋放鎖
; - 當線程進入到 monitorenter 指令後,線程將會持有 Monitor 對象,退出 monitorenter 指令後,線程將會釋放 Monitor 對象;
- hello 方法有兩種結束方式:正常執行,異常結束,所以退出路徑有兩個,有兩個 monitorexit 指令
- world 方法主動拋出異常,所以沒有正常執行結束流程,只有一個 monitorexit 指令
P14_自旋對於synchronized關鍵字的底層意義與價值分析
- JVM 的同步是基於進入與退出
監視器對象
(管程對象Monitor
)來實現的,每個對象實例都會有一個 Monitor 對象,Monitor 對象會和 java 對象一同創建和銷燬,Monitor 對象是由 C++ 來實現的。 - 當多個線程同時訪問同一段同步代碼時,這些線程會被放到一個
EntryList 集合
中(處於阻塞狀態的線程也會在該列表中);當某一線程獲取到對象的 Monitor 對象時,Monitor 依賴於底層操作系統的mutex lock
來實現互斥,線程獲取mutex
成功,則會持有該mutex
,這時其他線程就無法再獲取到該 mutex 。 - 如果線程調用了 wait 方法,那麼該線程就會釋放所持有的 mutex,並且該線程會進入到
WaieSet 集合
(等待集合)中,等待下一次被其他線程調用 notify/notifyAll 喚醒。如果當前線程執行返回,也會釋放所持有的 mutex 。 - 同步鎖在這種實現方式當中,因爲 Monitor 是依賴於底層的操作系統實現,這樣就存在
用戶態
和內核態
之間的切換,所以會增加性能開銷
。 - 通過對象互斥鎖的概念來保證共享數據操作的完整性。每個對象都對應一個可稱爲
互斥鎖
的標記,這個標記用於保證在任何時刻,只能有一個線程訪問該對象。 - 那些處於 EntryList 和 WaitSet 中的線程均處於阻塞狀態,阻塞操作是由操作系統完成的,在 Linux 系統下是通過
pthread_mutex_lock 函數
實現的。線程被阻塞後便會進入到內核調度
狀態,這會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能。 - 解決上述問題的辦法便是
自旋
。其原理是:當發生對 Monitor 的爭搶時,若Owner(持有 Monitor 的線程)
能夠在很短時間內釋放掉鎖,則那些正在爭搶鎖的線程就可以稍微等待一下(即所謂的自旋),在 Owner 線程釋放鎖之後,自旋的線程可能會立即獲取到鎖,從而避免阻塞。不過,當 Owner 線程運行時間超過臨界值後,自旋線程自旋一段時間依然無法獲取到鎖,這時自旋線程則會停止自旋而進入到阻塞狀態。所以總體思想是:先自旋,不成功再進行阻塞,儘量降低阻塞的可能性
,這對那些執行時間很短的代碼塊來說有極大的性能提升。顯然,自旋(需要消耗 CPU 性能)在多處理器(多核心)上纔有意義。
P15_互斥鎖屬性詳解與Monitor對象特性解說
互斥鎖的屬性:
- PTHREAD_MUTEX_TIMED_NP
屬性缺省值
,普通鎖
: 當一個線程加鎖之後,其餘請求鎖的線程將會形成一個等待隊列,並且在解鎖後按照優先級獲取到鎖,這種策略可以確保資源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP
嵌套鎖
: 允許一個線程對同一個鎖成功獲取多次
,並通過 unlock 解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新進行競爭。
- PTHREAD_MUTEX_ERRORCHECK_NP
檢錯鎖
: 如果一個線程請求同一個鎖,則返回 EDEADLK
,否則與 PTHREAD_MUTEX_TIMED_NP 類型動作相同,這樣就保證了當不允許多次加鎖時不會出現最簡單情況下的死鎖。
- PTHREAD_MUTEX_ADAPTIVE_NP
適應鎖
: 動作最簡單的鎖,僅僅等待解鎖後重新競爭。