一、synchronized加鎖原理
由於synchronized總體的工作原理是通過操作對象頭來實現加鎖和解鎖,因此在具體的瞭解synchronized之前,首先需要簡單瞭解一下對象頭。
所謂對象頭其實是每個對象都存在的一份存儲當前對象gc分代年齡、hashcode、鎖標記、當前獲取到鎖的線程ID等信息的一塊內存區域。對象頭主要分爲以下三塊:
1.Mark Word
2.指向類的指針
3.數組長度(只有數組對象纔有)
synchronized在加鎖過程中主要對mark word進行操作,因此主要對mark word的劃分,各個劃分快存儲的內容進行探索。如下圖所示mark word的劃分以及各個劃分塊存儲的內容:
在64位jvm中mark word的長度爲64bit,從圖中我們可以看出最後兩位用來作爲鎖標記位,在無鎖和偏向鎖狀態時還存在1bit的偏向鎖位用來標記是否是可偏向的。
我們可以使用jol來查看對象頭信息,要是有jol需要引入如下包:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
執行以下代碼我們可以簡單看一下對象頭信息:
Object o=new Object();
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(o).toPrintable());
執行結果:
從執行結果我們可以看到對象頭總共佔了96bit【空框1】,其中mark Word佔了64bit【紅框2】,最後三位的狀態爲001說明當前處於無鎖狀態。
從上邊我們知道了synchronized在加鎖解鎖時操作的內存空間,那麼synchronized是如何操作的,在什麼時候進行操作,操作了什麼內容呢,下文將進一步瞭解。
使用javap反編譯工具對含有synchronized代碼塊的代碼進行反彙編,執行javap -v App.class的運行結果:
可以看到在被synchronized包起來的代碼塊前後增加了monitorenter和monitorexit兩條彙編指令,當代碼執行到monitorenter指令時開始加鎖操作,大概執行過程如下:
在代碼進入代碼塊的時候,如果此同步對象沒有被鎖定(鎖標誌位爲“01”狀態),虛擬機首先將在當前線程的棧幀中建立一個名爲Lock Record的空間,用於存儲鎖對象目前的Mark Word的拷貝,然後,虛擬機將使用CAS操作嘗試將對象的Mark Word更新爲Lock Record的指針。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且隊形Mark Word的鎖標誌位(Mark Word的最後兩個bits)將轉變爲‘00’,即表示此對象處於輕量級鎖定的狀態。
—深入理解JVM
二、synchronized鎖膨脹過程
鎖膨脹是從偏向鎖—>輕量級鎖---->重量級鎖的轉換過程。在探究膨脹過程之前,我們先簡單瞭解一下偏向鎖、輕量級鎖、重量級鎖的概念。
①重量級鎖:傳統重量級鎖指使用操作系統互斥量來實現加鎖(PV操作),使用這種鎖的話,即使沒有所競爭的時候還是會調用操作系統級別的互斥量實現加鎖,造成性能消耗。
②輕量級鎖:通過簡單的數據操作實現加鎖,不需要調用OS級別的加鎖機制。
*在當前線程自己的棧區創建Lock Record用於存儲Object的Mark Word的拷貝
*將Mark Word副本存入lock record
*基於CAS的機制將Object的Mark Word跟新爲執行Lock record的指針
*如果CAS跟新成功,則當前線程獲取到鎖,否則檢查Mark Word出的指針是否指向自己的棧針
*如果指向自己的棧針,則說明已經獲取到鎖,則重入代碼快執行,否則說明鎖已經被佔用,則膨脹爲重量級鎖
③偏向鎖:偏向鎖可以說是在輕量級鎖上的進一步優化,輕量級鎖在沒有鎖競爭的條件下還需要執行CAS操作,偏向鎖連CAS都不操作。所謂“偏”就是說偏袒於第一次獲得鎖的線程,如果在接下來的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將不需要執行同步操作,直接獲得運行資源。直到另外一個線程到來,偏向鎖開始膨脹。
1.配置虛擬機開啓偏向鎖
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0