Java 虛擬機器對 synchronized 鎖的優化
一、volatile 與 synchronized 關鍵字 的原理
Java程式碼首先會被編譯成位元組碼檔案。位元組碼檔案被載入到JVM中,JVM將位元組碼翻譯成彙編指令,從而在CPU中執行。
談到volatile關鍵字,就會想到兩點:
(1)讓變數在多個執行緒之間可見,並且強制執行緒從公共堆疊中取得變數的值(常見的從變數和執行緒的角度分別闡釋)。
(2)禁止JVM指令重排序優化。
上述只是現象,原理可以粗略理解為:強制執行緒把修改的資料寫回主記憶體,並且讓其它執行緒的相關快取失效,需要從主存中重新讀取
synchronized本質其實就是獲取物件的監視器,這個過程是排他的,同一時刻只有一個執行緒能夠獲取到同步鎖物件的監視器。 獲取物件監視器的執行緒失敗後,將會回到同步佇列,其執行緒的狀態進入BLOCKED狀態。
二、synchronized關鍵字的優化
CAS可以粗略的理解為 "讀改寫" 操作。
物件的結構簡單示意:物件由 “物件頭” 和 “物件例項”組成。物件頭 由“ 鎖狀態”等資訊組成。
synchronized 用的鎖是存在 Java 物件頭裡的。
鎖的狀態有4種,級別從低到高依次是: 無鎖狀態
(1)偏向鎖:如果一個執行緒獲得了鎖,那麼鎖就會進入偏向模式。當這個執行緒再次請求鎖的時候,無需在做任何同步操作。這樣就節省了大量有關鎖申請的操作,從而提高了程式的效能。
因此,對於幾乎沒有鎖競爭的場合,偏向鎖有比較好的優化效果,因為連續多次極有可能是同一個執行緒請求相同的鎖。而對於鎖競爭比較激烈的場合,其效果不佳。因為在競爭激烈的場合,最有可能的情況是每次都是不同的執行緒來請求相同的鎖。這樣偏向模式會失效,因此還不如不啟用偏向鎖。使用Java虛擬機器引數-XX:+UseBiasedLocking可以開啟偏向鎖。
(2)輕量級鎖:如果偏向鎖失敗,虛擬機器並不會立即掛起執行緒。它還會使用一種稱為輕量級鎖的優化手段。輕量級鎖的操作也很輕便,它只是簡單地將物件頭部作為指標,指向持有鎖的執行緒堆疊的內部,來判斷一個執行緒是否持有物件鎖。如果執行緒獲得輕量級鎖成功,則可以順利進入臨界區。如果輕量級鎖加鎖失敗,則表示其他執行緒搶先爭奪到了鎖,那麼當前執行緒的鎖請求就會膨脹為重量級鎖。競爭的執行緒不會真正的阻塞,它通過不斷的自旋來爭取同步鎖,相當於還沒有待機。
(3)自旋鎖:輕量級鎖就會膨脹為重量級鎖後,虛擬機器為了避免執行緒真實的在作業系統層面掛起,虛擬機器還會在做最後的努力--自旋鎖。若經過幾個空迴圈可以獲取到鎖則進入臨界區,如果還是獲取不到則系統會真正的掛起執行緒。
(4)重量級鎖:執行緒沒有爭取到鎖,直接進入BLOCKED狀態,再次啟動去爭取同步鎖較為消耗時間。
為什麼鎖的升級無法逆向?
自旋鎖無法預知到底會空迴圈幾個時鐘週期,並且會很消耗 CPU,為了避免這種無用的自旋操作,一旦鎖升級為重量鎖,就不會再恢復到輕量級鎖
.