相信對於各位java大佬,synchronized關鍵字大家可能並不陌生,今天這邊來詳細聊聊這個synchronized關鍵字。
相信有有寫java併發基礎的同學可能知道,synchronized關鍵字是利用鎖的機制來實現代碼同步的。對於java中的鎖機制有如下兩個特性(還是要了解下的,吹牛逼的時候好提升下b格)
互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(複合操作)進行訪問。互斥性我們也往往稱爲操作的原子性。
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。
關於synchronized關鍵字,有如下幾種用法:
根據修飾對象分類
1、同步方法
(1) 同步非靜態方法
Public synchronized void methodName(){
……
}
(2) 同步靜態方法
Public synchronized static void methodName(){
……
}
2、同步代碼塊
synchronized(this|object) {}
synchronized(類.class) {}
Private final Object MUTEX =new Object();
Public void methodName(){
Synchronized(MUTEX ){
……
}
}
關於這兩種方式具體有什麼區別呢?
在說這個之前,我們先來講講對象鎖和類鎖:
(1)獲取對象鎖(修飾非靜態方法):synchronized(this|object) {}
在 Java 中,每個對象都有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,通常被稱爲“內置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨立的對象鎖,互不干擾。
(2)獲取類鎖(修飾靜態方法):synchronized(類.class) {}
在 Java 中,針對每個類也有一個鎖,可以稱爲“類鎖”,類鎖實際上是通過對象鎖實現的,即類的 Class 對象鎖。每個類只有一個 Class 對象,所以每個類只有一個類鎖。
在 Java 中,每個對象都會有一個 monitor 對象,監視器。
-
某一線程佔有這個對象的時候,先monitor 的計數器是不是0,如果是0還沒有線程佔有,這個時候線程佔有這個對象,並且對這個對象的monitor+1;如果不爲0,表示這個線程已經被其他線程佔有,這個線程等待。當線程釋放佔有權的時候,monitor-1;
-
同一線程可以對同一對象進行多次加鎖,+1,+1,重入性,因此synchronized也可以理解爲一把可重入鎖
下面我麼便來驗證下上面的,這裏介紹一個java反編譯命令:javap -v
baomw@baomingwudeMacBook-Air:~/IdeaProjects/test1/target/classes/com/baomw$ javap -v SyncTest.class
Classfile /Users/baomw/IdeaProjects/test1/target/classes/com/baomw/SyncTest.class
Last modified 2019-3-17; size 763 bytes
MD5 checksum d728833a93ce9cf96516976dd96d5ed7
Compiled from "SyncTest.java"
public class com.baomw.SyncTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#30 // com/baomw/SyncTest.num:I
#3 = Methodref #4.#31 // com/baomw/SyncTest.add:()V
#4 = Class #32 // com/baomw/SyncTest
#5 = Class #33 // java/lang/Object
#6 = Utf8 num
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/baomw/SyncTest;
#15 = Utf8 add
#16 = Utf8 remove
#17 = Utf8 StackMapTable
#18 = Class #32 // com/baomw/SyncTest
#19 = Class #33 // java/lang/Object
#20 = Class #34 // java/lang/Throwable
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 MethodParameters
#26 = Utf8 <clinit>
#27 = Utf8 SourceFile
#28 = Utf8 SyncTest.java
#29 = NameAndType #8:#9 // "<init>":()V
#30 = NameAndType #6:#7 // num:I
#31 = NameAndType #15:#9 // add:()V
#32 = Utf8 com/baomw/SyncTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/Throwable
{
public com.baomw.SyncTest();
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 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baomw/SyncTest;
public static synchronized void add();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field num:I
3: iconst_1
4: iadd
5: putstatic #2 // Field num:I
8: return
LineNumberTable:
line 18: 0
line 19: 8
public void remove();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field num:I
7: iconst_1
8: isub
9: putstatic #2 // Field num:I
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 23: 0
line 24: 4
line 25: 12
line 26: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/baomw/SyncTest;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/baomw/SyncTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #3 // Method add:()V
3: return
LineNumberTable:
line 29: 0
line 30: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #2 // Field num:I
4: return
LineNumberTable:
line 15: 0
}
SourceFile: "SyncTest.java"
測試代碼發編譯後的指令如上:
如上,在計算機指令中,當synchronized修飾我們的方法時,在方法入口增加ACC_SYNCHRONIZED 對整個方法加鎖
而在修飾我們的代碼塊時,在代碼塊的執行入口和出口處增加Monitorenter和
Monitorexit,注意這兩個總是成對出現的。
當然最後在使用我們的synchronized的時候,以下問題還是需要注意下的:
(1)與moniter關聯的對象不能爲空
這個應該不用多說,因爲我們的每一個java對象都和一個monitor關聯,既然對象都爲空了,monitor也就無從談起了。
(2)synchronized作用域太大
在使用中應該儘可能減少鎖的粒度,不然作用域太大,作用域內的代碼很可能還會出現併發問題,其次,代碼都變串行執行,效率低
(3)不同的monitor企圖鎖相同的方法
(4)多個鎖的交叉導致死鎖
好了關於synchronized關鍵子暫時先講到這裏,後續有相關知識再來補充。