synchronized優化原理
輕量級鎖
如果一個對象有多個線程訪問,但多線程訪問的時間是錯開的(沒有競爭),可以用輕量級鎖優化
@Slf4j(topic = "c.ExerciseTransfer")public class Test {?static final Object obj = new Object();public static void main(String[] args) throws InterruptedException {synchronized (obj){method();}}?public static void method(){synchronized (obj){}}}
讓鎖記錄中 Object reference 指向鎖對象,并嘗試用 cas 替換 Object 的 Mark Word,將 Mark Word 的值存 入鎖記錄
如果 cas 替換成功,對象頭中存儲了 鎖記錄地址和狀態 00 ,表示由該線程給對象加鎖,這時圖示如下。
如果 cas 失敗,有兩種情況
-
如果是其它線程已經持有了該 Object 的輕量級鎖,這時表明有競爭,進入鎖膨脹過程
-
如果是自己執行了 synchronized 鎖重入,那么再添加一條 Lock Record 作為重入的計數
當退出 synchronized 代碼塊(解鎖時)如果有取值為 null 的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一。
當退出 synchronized 代碼塊(解鎖時)鎖記錄的值不為 null,這時使用 cas 將 Mark Word 的值恢復給對象頭
-
成功,則解鎖成功
-
失敗,說明輕量級鎖進行了鎖膨脹或已經升級為重量級鎖,進入重量級鎖解鎖流程
鎖膨脹
如果在嘗試加輕量級鎖的過程中,CAS 操作無法成功,這時一種情況就是有其它線程為此對象加上了輕量級鎖(有 競爭),這時需要進行鎖膨脹,將輕量級鎖變為重量級鎖。
這時 Thread-1 加輕量級鎖失敗,進入鎖膨脹流程
-
即為 Object 對象申請 Monitor 鎖,讓 Object 指向重量級鎖地址
-
然后自己進入 Monitor 的 EntryList BLOCKED
當 Thread-0 退出同步塊解鎖時,使用 cas 將 Mark Word 的值恢復給對象頭,失敗。這時會進入重量級解鎖 流程,即按照 Monitor 地址找到 Monitor 對象,設置 Owner 為 null,喚醒 EntryList 中 BLOCKED 線程。
自旋優化
重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前線程自旋成功(即這時候持鎖線程已經退出了同步塊,釋放了鎖),這時當前線程就可以避免阻塞。
偏向鎖
輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍然需要執行 CAS 操作。
Java 6 中引入了偏向鎖來做進一步優化:只有第一次使用 CAS 將線程 ID 設置到對象的 Mark Word 頭,之后發現這個線程 ID 是自己的就表示沒有競爭,不用重新 CAS。以后只要不發生競爭,這個對象就歸該線程所有。例如:
static final Object obj = new Object();public static void m1() {synchronized( obj ) {// 同步塊 Am2();}}public static void m2() {synchronized( obj ) {// 同步塊 Bm3();}}public static void m3() {synchronized( obj ) {// 同步塊 C}}
偏向狀態
之所以要用偏向鎖是因為輕量級鎖的鎖重入每次都調用CAS進行對比,CAS是一個OS指令操作,速度很慢。所以偏向鎖是把ThreadId直接賦值給markword,那么下次能直接在java上對比這個markword。
-
偏向鎖帶有延遲性,通常對象創建過一會才會生成
-
先生成偏向鎖->輕量級鎖->重量級鎖
-
如果給臨時區使用偏向鎖,那么對應執行線程的id賦值給markword
-
如果使用了鎖的hashcode,那么偏向鎖就會被禁止,因為hashcode占用的bit太多。
-
輕量級在鎖記錄上記錄hashcode,重量級在monitor上記錄
-
如果兩個線程用同一個偏向級鎖,那么鎖會變成不可偏向,升級為輕量級鎖。