synchronized真的很重麼?

三友的java日記發表於2022-06-01

synchronized 是java中常見的保證多執行緒訪問共享資源時的安全的一個關鍵字。很多人在講到synchronized 時都說synchronized 是一把重量級的鎖,那麼synchronized 真的很重麼?

synchronized 在jdk 1.6以前(不包括1.6)的確是一把很重的鎖,每次使用鎖的時候都是直接向作業系統請求的,所以效率低,且佔資源,但是在jdk1.6以後,jvm對synchronized 進行了優化,加入了鎖升級的功能,使得synchronized 在一些情況下並不是一把重量級的鎖,而是一個很輕的一把鎖。

本文就來探討一下,jdk對synchronized 優化,包括鎖升級、鎖粗化、鎖消除。

一、鎖升級

鎖升級其實是指,隨著多執行緒併發加鎖的程度提高而相應的對鎖的狀態的升級,可以分為:偏向鎖、輕量級鎖、自旋鎖、重量級鎖;

偏向鎖

偏向鎖不是一把鎖,而是代表了當前synchronized 鎖狀態。偏向鎖是一把很輕的鎖,當只有一個來執行緒加鎖的時候,此時synchronized 鎖就會變成偏向鎖,偏向鎖代表這個鎖偏向這個執行緒,就是說當這個執行緒再次來加鎖的時候,不需要再向作業系統申請資源,而是很快就能獲取到鎖,減少了申請鎖的開銷。

為什麼需要偏向鎖。因為在大多數情況下,多執行緒競爭同一把鎖的情況是很少的,那麼在沒有多執行緒併發競爭的情況下,其實沒必要再去向系統申請重量級鎖了,我就用目前這把偏向鎖就夠了,因為申請重量級會耗費比較大的資源。

輕量級鎖

當隨著更多執行緒來加鎖的時候,偏向鎖就會無法滿足使用的條件了,因為偏向鎖認為加鎖的執行緒只有一個。

那麼多執行緒加鎖有可能會出現這種情況。當會有兩個及以上的執行緒來加鎖,但是沒有出現同時來競爭鎖的情況,也就是說雖然有多個執行緒來加鎖,可能會出現A執行緒加完鎖之後釋放了鎖,此時B來加鎖,發現並沒有執行緒持有鎖,也就說沒有執行緒跟B來競爭,也就相當於多執行緒來交替加鎖的情況。

當出現這種情況的時候,偏向鎖就會升級為輕量級鎖。輕量級鎖就是指雖然可能會出現多執行緒來加鎖的情況,但是並不存在鎖競爭的情況,並不會存在鎖衝突。

自旋鎖

上面說到隨著加鎖的執行緒變多,出現了多執行緒交替加鎖的情況,偏向鎖會升級為輕量級鎖,但是隨著併發加鎖的執行緒越來越多,出現了多個執行緒同時來加鎖的情況,也就不是交替加鎖,那麼此時輕量級鎖已經不適合使用了,但是jvm為了防止鎖直接升級為重量級鎖(因為掛起執行緒和恢復執行緒的操作都需要轉入到核心態中完成,這些操作給系統的併發效能帶來很大的壓力),加入了執行緒自旋的機制。所謂的自旋就是雖然加鎖失敗了,但是有可能出現其他執行緒很快就釋放鎖的情況,那麼就嘗試不斷的自旋來嘗試加鎖,而不是直接將鎖升級為重量級鎖。

自旋鎖的好處就是用來減少作業系統的壓力。

重量級鎖

但是隨著加鎖的執行緒不斷增多,自旋了一定的時間或者次數也沒有成功加鎖,那麼鎖就會升級為重量級鎖,因為自旋會消耗cpu,一直這麼自旋也不是很好的選擇,所以就會升級為重量級鎖。升級為重量級鎖之後,所有來加鎖的執行緒加鎖失敗之後,就會加入等待的佇列中,等待別的執行緒釋放鎖之後再進行鎖的競爭。

通過以上的分析,我們也看出了,synchronized 在最開始的時候並不是上來就是一把重量級的鎖,而是隨著多執行緒併發競爭鎖的激烈程度的提高來不斷的升級,慢慢變成重量級鎖,在整個升級的過程會經歷偏向鎖、輕量級鎖、自旋鎖、重量級鎖的過程。

二、鎖消除

先來看一段代

public class SynchronizedDemo {

    public static void main(String[] args) {
        Object monitor = new Object();
        synchronized (monitor){
            System.out.println("加鎖成功....");
        }
    }
}

  

通過上面程式碼我們可以看出,synchronized 的鎖物件其實是方法的一個區域性變數,那麼對於這種情況,不會出現多執行緒競爭同一把Object 物件鎖的情況,那麼在執行的時候就會忽略這個synchronized 加鎖的過程。

說的官方一點就是指虛擬機器即時編譯器在執行時,對一些程式碼上要求同步,但是被檢測到不可能存在共享資料競爭的鎖進行消除。

三、鎖粗化

public class SynchronizedDemo {

    private static final Object MONITOR = new Object();

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            synchronized (MONITOR) {
                System.out.println("加鎖成功....");
            }
        }
    }
}

  

通過這段程式碼的分析,可以看出,迴圈內部每次都對同一個物件進行加鎖和解鎖,對於這種一串零碎的操作都對同一個物件加鎖情況,虛擬機器將會把加鎖同步的範圍擴充套件 (粗化)到整個操作序列的外部。以上述程式碼為例,也就是擴充套件到把for迴圈這個操作加鎖,這樣只需要加鎖一次就可以了。

總結

所以,通過本篇文章可以看出,jdk對synchronized 其實進行了一系列的優化來儘可能減少加鎖時對於效能的消耗,包括鎖升級、鎖消除、鎖粗化。希望通過本篇文章可以讓你對synchronized 底層又有一個全新的認識。

如果覺得這篇文章對你有所幫助,還請幫忙點贊、在看、轉發一下,碼字不易,非常感謝!

如果覺得這篇文章對你有所幫助,還請幫忙點贊、在看、轉發給更多的人,碼字不易,非常感謝!

往期熱門文章推薦

掃碼或者搜尋關注公眾號 三友的java日記 ,及時乾貨不錯過,公眾號致力於通過畫圖加上通俗易懂的語言講解技術,讓技術更加容易學習。 

最近花了一個月的時間,整理了這套併發程式設計系列的知識點。涵蓋了 volitile、synchronized、CAS、AQS、鎖優化策略、同步元件、資料結構、執行緒池、Thread、ThreadLocal,幾乎覆蓋了所有的學習和麵試場景,如圖。

 

 

 

文件獲取方式:

連結:https://pan.baidu.com/s/129wZe3ywAUsjOqTU037Kmg
提取碼:aps9

相關文章