目錄
1.功能
synchronized是用與線程同步,是一個重量級互斥鎖,可以保證方法或者代碼塊在運行時,同一時刻只有一個線程可以進入到該對象的臨界區(同一個對象的所有synchronized保衛的代碼塊)。
synchronized是可重入的,即同一個線程,可以多次進入同個對象的多個synchronized塊。
2.鎖對象
synchronized修飾位置 | 鎖對象 | 樣例 |
---|---|---|
實例方法 | 當前實例對象 this | public synchronized void synMethod() |
靜態方法 | 當前類的Class對象 | public static synchronized void doSth() |
代碼塊 |
括號裏面的對象, 既可以是實例對象 也可以是類對象 |
synchronized (lock) {...} synchronized (lock.getClass()) {...} |
3.JVM實現
下面看下一個同步塊編譯後的虛擬機指令是什麼樣的,
方法 | .class類文件指令 |
---|---|
|
public void synBlock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: getfield #2 // Field x:I 8: bipush 12 10: if_icmpge 19 13: aload_0 14: bipush 12 16: putfield #2 // Field x:I 19: aload_1 20: monitorexit 21: goto 29 24: astore_2 25: aload_1 26: monitorexit 27: aload_2 28: athrow 29: return Exception table: from to target type 4 21 24 any... LineNumberTable: line 15: 0 .... LocalVariableTable: Start Length Slot Name Signature 0 30 0 this .... |
synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指向同步代碼塊的開始位置,monitorexit 指向結束位置。
sum.misc.Unsafe類中也包含類似方法,都是JNI調用,
public native void monitorEnter(Object o);
public native void monitorExit(Object o);
3.1 Java對象頭
|
|
3.1.1 Mark Word
Mark Word是動態定義的數據結構,在對象不同鎖狀態時,存儲不同的數據,以便減少內存消耗
鎖狀態 | Mark Word | ||||
---|---|---|---|---|---|
未鎖定 Normal | hashcode:25 哈希碼 |
age:4 分代年齡 |
biased_lock:1 0 |
lock:2 01 |
|
偏向鎖 Biased | thread:23 偏向線程 |
epoch:2 偏向時間戳 |
age:4 分代年齡 |
biased_lock:1 1 |
lock:2 01 |
輕量級鎖 Light | ptr_to_lock_record:30 指向鎖記錄的指針 |
lock:2 00 |
|||
重量級鎖 Heavy | ptr_to_heavyweight_monitor:30 指向重量級鎖的指針 |
lock:2 10 |
|||
GC標記 | 30 空 |
lock:2 11 |
3.1.2 Klass Word
3.2 鎖狀態
jdk1.6對synchronized進行了優化,引入了自旋鎖、偏向鎖、輕量級鎖、重量級鎖的概念。
自旋鎖:在未能獲取鎖的時候,該線程進入循環體空跑一段時間,如果跑完正好能夠獲取鎖就能避免切換線程等消耗。
偏向鎖、輕量級鎖和重量級鎖的轉化如下圖,下面一一介紹這3種鎖。
在代碼即將進入同步塊的時候,如果此同步對象沒有被鎖定(lock爲“01”狀態),且biased_lock=0
-XX:+UseBiased Locking 偏向鎖可用 |
1.lock=01,biased_lock=1, 2.CAS操作threadID到mark word(前23bit) |
---|---|
偏向鎖不可用,(jdk6以前) 用輕量級鎖 |
1.在棧上鎖記錄lock record拷貝mark word 2.CAS操作lock record指針到mark word |
鎖膨脹到重量級鎖
|
3.2.1 偏向鎖 01
偏向鎖也是JDK 6中引入的一項鎖優化措施,目的是消除在無競爭情況下的同步原語,有些連CAS操作都不去做,進一步提高運行性能。偏向鎖可以通過-XX:+UseBiased Locking參數選擇開啓,從JDK6後默認開啓。
線程競爭圖解 | 解釋 |
---|---|
對比對象頭是否有當前線程的id, 如果有時則直接重入,此時無需CAS (基於大多數情況不會鎖競爭,只是線程自己重入) 否則重新獲取鎖,無鎖狀態,且偏向鎖可用 |
|
當鎖對象第一次被線程1獲取的時候(CAS), 虛擬機將會把對象頭中的鎖標誌位=“01”、偏向模式=“1”, 表示進入偏向模式。 一旦另外一個線程2先判斷線程id是否匹配, 不匹配就競爭這個鎖,偏向模式就馬上撤銷。 |
|
撤銷偏向(偏向模式設置爲“0”), 對象未鎖定:標誌位恢復到未鎖定(標誌位爲“01”) 對象已鎖定:輕量級鎖定(標誌位爲“00”) |
CAS
原子操作Compare And Swap,CAS(V,A,B) ,邏輯如下,只是通過底層操作系統的原子指令操作
boolean CAS(int V, int A, int B) {
if (V == A) {
V = B;
return true;
} else {
return false;
}
}
當內存中的V值等於預期A值時,則將內存中的V值更新爲B值,並返回true,否則返回false。
偏向鎖CAS(markword, hash-0-01,t1-id-1-01),
僅當expect A是hash-0-01時纔會更新爲t1-id-1-01,併發CAS時只有第一個線程t1才能獲取成功,
t2去CAS時會因爲markwork與expect值hash-0-01不一致,操作失敗返回false
3.2.2 輕量級鎖 00
輕量級鎖同過CAS操作
線程競爭圖解 | 解釋 |
---|---|
發生 不可偏向情況 或者偏向後發生競爭 升級到輕量級鎖 |
|
t1獲取輕量級鎖成功, t2併發競爭失敗,此時發生自旋,等待輕量級鎖 |
|
虛擬機首先將在當前線程的棧幀中建立一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,如圖所示。
使用CAS(Mark Word,hash-0-01,ptr2LR-00) ,更新爲指向Lock Record的指針和鎖標誌00。如果這個更新動作成功了,即代表該線程擁有了這個對象的鎖,此對象處於輕量級鎖定狀態。
3.2.3 重量級鎖
依賴與底層操作系統的mutex lock互斥鎖實現,需要從用戶態切換到內核態,切換成本非常高。
一旦升級到重量級鎖,其它線程競爭鎖時,就直接進入阻塞隊列。