synchronized是Java提供的原子性內建鎖,這種內建的並且使用者看不到的鎖也被稱為監視器鎖。
synchronized透過在程式碼塊前後加上monitorenter和monitorexit位元組碼指令用於實現進入和退出。
如果是同步方法,則是打上標記,隱式的使用monitorenter和monitorexit位元組碼指令。
在jdk1.5之前,加鎖和釋放鎖是由作業系統來實現的,系統呼叫涉及到上下文切換,效能消耗極高,多以當時synchronized效能極差並且是公認的重量級鎖。
在jdk1.6之後,引入了偏向鎖和輕量級鎖兩個概念,透過鎖升級策略來解決效能問題。
為了講清楚Synchronized工作原理,需要理解以下幾個問題:
-
程式 = 資料結構 + 演算法,鎖也是屬於一種程式,所以它應該有對應的資料結構 + 演算法。
-
Java物件記憶體佈局,即synchronized使用了怎麼樣的資料結構?
每個Java物件在記憶體中的佈局通常分為三部分:物件頭、例項資料、對齊填充。物件頭中包含Mark Word和Class Point
重點就是Mark Word,它包含一下資訊:
-
雜湊碼
-
GC標誌
-
鎖標誌位:判斷當前物件處於什麼鎖狀態(無鎖、偏向鎖、輕量級鎖、重量級鎖)
-
偏向鎖的執行緒ID
-
輕量級鎖的棧地址指標
-
重量級鎖的Monitor指標
-
-
怎麼理解每個物件都有成為鎖的潛質?
由於每個物件都有Mark Word,都可以儲存鎖狀態,都是可以支撐鎖演算法的資料結構,所以每個物件都有成為鎖的潛質。
-
物件是怎麼透過Mark Word成為一把鎖的?即怎麼使用這個資料結構標識不同的鎖狀態?
無鎖:當一個物件新建時,沒有被任何物件鎖定,此時鎖標誌位01
偏向鎖:第一個執行緒獲取物件鎖,鎖標誌位01,偏向標誌位標記為1,並在物件頭中記錄鎖的執行緒ID,如果沒有競爭,下次直接根據ID直接獲取,即可重入,避免了頻繁CAS。
輕量級鎖:發現鎖競爭,即兩個執行緒以上時,偏向鎖取消,升級為輕量級鎖,此時執行緒會在棧幀中建立一個鎖記錄,並嘗試透過CAS將物件頭的輕量級鎖棧地址指標指向鎖記錄,成功則獲取到了鎖。鎖記錄中記錄了重入次數,同時修改鎖標誌位,記錄鎖狀態。
重量級鎖:當競爭激烈,CAS次數超過重試閾值時,JVM決定升級為重量級鎖,此時JVM建立Monitor物件或者使用已有的Monitor物件,並將Monitor指標指向Monitor物件,後續交由作業系統進行排程。
Monitor物件
Monitor物件不是原始Java物件,而是JVM內部使用的資料結構。它用於實現重量級鎖的管理,通常包含以下欄位:
- Owner:當前持有鎖的執行緒。
- Recursion Count:重入計數器,記錄當前執行緒的重入次數。
- Entry List:等待獲取鎖的執行緒佇列。
- Wait Set:等待某個條件的執行緒佇列(通常用於
wait
和notify
機制)。
執行緒進入同步程式碼塊時,首先進入entryList,獲取到鎖後就更改Owner,並且計數器加1。
如果執行緒wait了,就會釋放鎖,釋放鎖之後就進入Wait Set,只有透過notify或者notifyAll才能喚醒,喚醒之後又進入entryList排隊競爭鎖。
獲取到鎖的執行緒執行完畢就釋放鎖,計數器減1,當前執行緒Owner置Null