synchronized 鎖對象
普通方法
synchronized 鎖普通方法時,其鎖的對象是調用該方法的實例
public synchronized void method() { // 方法體
}
靜態方法
靜態方法的鎖對象是所屬的 class,全局只有一個。
public static synchronized void staticMethod() { // 方法體
}
同步代碼塊
鎖對象為括號內的指定對象。
synchronized(this) { // 代碼塊
}
synchronized 特性
有序性
讀讀,讀寫,寫讀,寫寫互斥。
可見性
可見性是指多個線程訪問一個資源時,該資源的狀態,值等對于其他線程都是可見的。synchronized 和 volatile 都具有可見性,其中 synchronized 對一個類或對象加鎖時,一個線程如果要訪問該累或對象必須先獲得它的鎖。這個鎖的狀態對于其他任何線程都是可見的,并且在釋放鎖之前會將對變量的修改刷新到共享內存中,保證資源變量的可見性。
原子性
原子性指的是同一時間只有一個線程去執行代碼,該操作是不能被其他線程打斷的,那么就具備了原子性。
synchronized的原子性本質上是線程互斥保證的原子性。
可重入性
同一線程在持有鎖的情況下,可多次獲取同一鎖而不會導致死鎖或阻塞其他線程。這種機制通過維護鎖的持有計數器實現,當線程首次獲取鎖時計數器設為1,每次重入增加計數,釋放時減少計數,直到歸零才釋放鎖。
synchronized 鎖升級的對象頭內容:
偏向鎖的意義和使用前提
偏向鎖就是在運行過程中,對象的鎖偏向某個線程。即在開啟偏向鎖機制的情況下,某個線程獲得鎖,當該線程下次再想要獲得鎖時,不需要重新申請獲得鎖(即忽略synchronized關鍵詞),直接就可以執行同步代碼,比較適合競爭較少的情況。
JDK 1.8 下加鎖會默認開啟偏向鎖。但是它在應用程序啟動幾秒后才會開啟,是存在延遲啟動的情況。因此可能在打印輸出加鎖時的信息會發現不符合偏向鎖的鎖標志。
當我們開啟了偏向鎖,并且沒有延遲開啟的時候,新創建的對象的 mark word 默認就是偏向鎖狀態的 markWord,只不過這個時候,因為沒有線程爭搶鎖,除了我們的鎖標志位和是否為偏向鎖標志位,其他都是 0
延遲的關閉和偏向鎖的關閉
延遲是可以關閉的。可以給 JVM 設置參數:-XX:BiasedLockingStartupDelay=0 來關閉延遲。
如果希望關閉偏向鎖:-XX:-UseBiasedLocking=false
偏向鎖細節
無鎖狀態下的 MarkWord 標志(看第一行的二進制數字):
01 表示偏向鎖,是由于開啟偏向鎖且沒有延遲開啟的情況下會顯示的,但是此時并沒有線程爭奪鎖。因此其他位置都是 0 ,僅僅是鎖標志位和是否為偏向鎖標志位有變化。
線程加上偏向鎖后:
根據上圖,偏向鎖加上了后會有標識線程 ID,Epoch 等信息,因此不全是 0 。
如果我們在加鎖前調用 hashcode 方法,會導致后續加鎖后變為輕量級鎖。
原理:
-
被加鎖的對象,沒有真正調用或者隱式的調用(比如使用 HashMap 放入當前對象,會調用 HashCode 方法)父類 Object 的 hashCode 方法,如果一旦調用了 hashCode 方法,對象頭里需要有一個存儲該 hashCode 值的位置。但是我們可以從上圖中看到,偏向鎖中并沒有地方進行 MarkWord 的保存,只有輕量級鎖才會有。
-
為了讓線程獲得鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID(對象頭:存儲線程 ID,棧幀的鎖記錄中:線程有自己的棧幀,LOCK RECORD: 存儲當前線程 ID),以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。(是否是當前線程的偏向鎖是通過ID來匹配的)
-
如果測試成功,表示線程已經獲得了鎖。
-
如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程(CAS 競爭,替換線程 ID)