首頁 > 軟體

Java synchronized偏向鎖的核心原理詳解

2022-03-01 19:00:22

1. 偏向鎖的核心原理

輕量級鎖在沒有競爭時(就自己這個執行緒),每次重入仍然需要執行 CAS 操作。 Java 6 中引入了偏向鎖來做進一步優化:只有第一次使用 CAS 將執行緒 ID 設定到物件的 Mark Word 頭,之後發現 這個執行緒 ID 是自己的就表示沒有競爭,不用重新 CAS。以後只要不發生競爭,這個物件就歸該執行緒所有。

public class Main {
    static final Object obj = new Object();
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
           m1();
        });
        thread.start();
    }
    public static void m1() {
        synchronized( obj ) {
            // 同步塊 A
            m2();
        }
    }
    public static void m2() {
        synchronized( obj ) {
            // 同步塊 B
            m3();
        }
    }
    public static void m3() {
        synchronized( obj ) {
            //偏向狀態
            // 同步塊 C
        }
    }
}

偏向鎖的核心原理是:如果不存線上程競爭的一個執行緒獲得了鎖,那麼鎖就進入偏向狀態,此時Mark Word的結構變為偏向鎖結構,鎖物件的鎖標誌位(lock)被改為01,偏向標誌位(biased_lock)被改為1,然後執行緒的ID記錄在鎖物件的Mark Word中(使用CAS操作完成)。以後該執行緒獲取鎖時判斷一下執行緒ID和標誌位,就可以直接進入同步塊,連CAS操作都不需要,這樣就省去了大量有關鎖申請的操作,從而也就提升了程式的效能。

偏向鎖的主要作用是消除無競爭情況下的同步原語,進一步提升程式效能,所以,在沒有鎖競爭的場合,偏向鎖有很好的優化效果。但是,一旦有第二條執行緒需要競爭鎖,那麼偏向模式立即結束,進入輕量級鎖的狀態。

假如在大部分情況下同步塊是沒有競爭的,那麼可以通過偏向來提高效能。即在無競爭時,之前獲得鎖的執行緒再次獲得鎖時會判斷偏向鎖的執行緒ID是否指向自己,如果是,那麼該執行緒將不用再次獲得鎖,直接就可以進入同步塊;如果未指向當前執行緒,當前執行緒就會採用CAS操作將Mark Word中的執行緒ID設定為當前執行緒ID,如果CAS操作成功,那麼獲取偏向鎖成功,執行同步程式碼塊,如果CAS操作失敗,那麼表示有競爭,搶鎖執行緒被掛起,復原佔鎖執行緒的偏向鎖,然後將偏向鎖膨脹為輕量級鎖。

偏向鎖的加鎖過程為:新執行緒只需要判斷內建鎖物件的Mark Word中的執行緒ID是不是自己的ID,如果是就直接使用這個鎖,而不使用CAS交換;如果不是,比如在第一次獲得此鎖時內建鎖的執行緒ID為空,就使用CAS交換,新執行緒將自己的執行緒ID交換到內建鎖的Mark Word中,如果交換成功,就加鎖成功。

每執行一輪搶佔,JVM內部都會比較內建鎖的偏向執行緒ID與當前執行緒ID,如果匹配,就表明當前執行緒已經獲得了偏向鎖,當前執行緒可以快速進入臨界區。所以,偏向鎖的效率是非常高的。總之,偏向鎖是針對一個執行緒而言的,執行緒獲得鎖之後就不會再有解鎖等操作了,這樣可以省略很多開銷。

偏向鎖的缺點:如果鎖物件時常被多個執行緒競爭,偏向鎖就是多餘的,並且其復原的過程會帶來一些效能開銷。

2. 偏向鎖的復原

假如有多個執行緒來競爭偏向鎖,此物件鎖已經有所偏向,其他的執行緒發現偏向鎖並不是偏向自己,就說明存在了競爭,嘗試復原偏向鎖(很可能引入安全點),然後膨脹到輕量級鎖。

偏向鎖復原的開銷花費還是挺大的,其大概過程如下:

(1) 在一個安全點停止擁有鎖的執行緒。

(2) 遍歷執行緒的棧幀,檢查是否存在鎖記錄。如果存在鎖記錄,就需要清空鎖記錄,使其變成無鎖狀態,並修復鎖記錄指向的Mark Word,清除其執行緒ID。

(3) 將當前鎖升級成輕量級鎖。

(4) 喚醒當前執行緒。

所以,如果某些臨界區存在兩個及兩個以上的執行緒競爭,那麼偏向鎖反而會降低效能。在這種情況下,可以在啟動JVM時就把偏向鎖的預設功能關閉。

復原偏向鎖的條件:

(1) 多個執行緒競爭偏向鎖。

(2) 呼叫偏向鎖物件的hashcode()方法或者System.identityHashCode()方法計算物件的HashCode之後,將雜湊碼放置到Mark Word中,內建鎖變成無鎖狀態,偏向鎖將被複原。

3. 偏向鎖的膨脹

如果偏向鎖被佔據,一旦有第二個執行緒爭搶這個物件,因為偏向鎖不會主動釋放,所以第二個執行緒可以看到內建鎖偏向狀態,這時表明在這個物件鎖上已經存在競爭了。JVM檢查原來持有該物件鎖的佔有執行緒是否依然存活,如果掛了,就可以將物件變為無鎖狀態,然後進行重新偏向,偏向為搶鎖執行緒。

如果JVM檢查到原來的執行緒依然存活,就進一步檢查佔有執行緒的呼叫堆疊是否通過鎖記錄持有偏向鎖。如果存在鎖記錄,就表明原來的執行緒還在使用偏向鎖,發生鎖競爭,復原原來的偏向鎖,將偏向鎖膨脹(INFLATING)為輕量級鎖。

4. 偏向鎖的好處

經驗表明,其實大部分情況下進入一個同步程式碼塊的執行緒都是同一個執行緒。這也是JDK會引入偏向鎖的原因。所以,總體來說,使用偏向鎖帶來的好處還是大於偏向鎖復原和膨脹所帶來的代價。

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容! 


IT145.com E-mail:sddin#qq.com