您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java中的synchronized鎖膨脹機制怎么實現”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java中的synchronized鎖膨脹機制怎么實現”吧!
在 JDK 1.5 時,synchronized 需要調用監視器鎖(Monitor)來實現,監視器鎖本質上又是依賴于底層的操作系統的 Mutex Lock(互斥鎖)實現的,互斥鎖在進行釋放和獲取的時候,需要從用戶態轉換到內核態,這樣就造成了很高的成本,也需要較長的執行時間,這種依賴于操作系統 Mutex Lock 實現的鎖我們稱之為“重量級鎖”。
用戶態(User Mode):當進程在執行用戶自己的代碼時,則稱其處于用戶運行態。 內核態(Kernel Mode):當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處于內核運行態,此時處理器處于特權級最高的內核代碼中執行。
假設沒有內核態和用戶態之分,程序就可以隨意讀寫硬件資源了,比如隨意讀寫和分配內存,這樣如果程序員一不小心將不適當的內容寫到了不該寫的地方,很可能就會導致系統崩潰。
而有了用戶態和內核態的區分之后,程序在執行某個操作時會進行一系列的驗證和檢驗之后,確認沒問題之后才可以正常的操作資源,這樣就不會擔心一不小心就把系統搞壞的情況了,也就是有了內核態和用戶態的區分之后可以讓程序更加安全的運行,但同時兩種形態的切換會導致一定的性能開銷。
在 JDK 1.6 時,為了解決獲取鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”的狀態,此時 synchronized 的狀態總共有以下 4 種:
無鎖
偏向鎖
輕量級鎖
重量級鎖
鎖的級別按照上述先后順序依次升級,我們把這個升級的過程稱之為“鎖膨脹”。
PS:到現在為止,鎖的升級是單向的,也就是說只能從低到高升級(無鎖 -> 偏向鎖 -> 輕量鎖鎖 -> 重量級鎖),不會出現鎖降級的情況。
鎖膨脹為什么能優化 synchronized 的性能?當我們了解了這些鎖狀態之后自然就會有答案,下面我們一起來看。
HotSpot 作者經過研究實踐發現,在大多數情況下,鎖不存在多線程競爭,總是由同一線程多次獲得的,為了讓線程獲得鎖的代價更低,于是就引進了偏向鎖。
偏向鎖(Biased Locking)指的是,它會偏向于第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下會給線程加一個偏向鎖。
當一個線程訪問同步代碼塊并獲取鎖時,會在對象頭的 Mark Word 里存儲鎖偏向的線程 ID,在線程進入和退出同步塊時不再通過 CAS 操作來加鎖和解鎖,而是檢測 Mark Word 里是否存儲著指向當前線程的偏向鎖,如果 Mark Word 中的線程 ID 和訪問的線程 ID 一致,則可以直接進入同步塊進行代碼執行,如果線程 ID 不同,則使用 CAS 嘗試獲取鎖,如果獲取成功則進入同步塊執行代碼,否則會將鎖的狀態升級為輕量級鎖。
偏向鎖是為了在無多線程競爭的情況下,盡量減少不必要的鎖切換而設計的,因為鎖的獲取及釋放要依賴多次 CAS 原子指令,而偏向鎖只需要在置換線程 ID 的時候執行一次 CAS 原子指令即可。
在 HotSpot 虛擬機中,對象在內存中存儲的布局可以分為以下 3 個區域:
對象頭(Header)
實例數據(Instance Data)
對齊填充(Padding)
對象頭中又包含了:
Mark Word(標記字段):我們的偏向鎖信息就是存儲在此區域的。
Klass Pointer(Class 對象指針)
對象在內存中的布局如下:
在 JDK 1.6 中默認是開啟偏向鎖的,可以通過“-XX:-UseBiasedLocking=false”命令來禁用偏向鎖。
引入輕量級鎖的目的是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統 Mutex Lock(互斥鎖)產生的性能消耗。如果使用 Mutex Lock 每次獲取鎖和釋放鎖的操作都會帶來用戶態和內核態的切換,這樣系統的性能開銷是很大的。
當關閉偏向鎖或者多個線程競爭偏向鎖時就會導致偏向鎖升級為輕量級鎖,輕量級鎖的獲取和釋放都通過 CAS 完成的,其中鎖獲取可能會通過一定次數的自旋來完成。
需要強調一點:輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果同一時間多個線程同時訪問時,就會導致輕量級鎖膨脹為重量級鎖。
synchronized 是依賴監視器 Monitor 實現方法同步或代碼塊同步的,代碼塊同步使用的是 monitorenter 和 monitorexit 指令來實現的,monitorenter 指令是在編譯后插入到同步代碼塊的開始位置,而 monitorexit 是插入到方法結束處和異常處的,任何對象都有一個 Monitor 與之關聯,當且一個 Monitor 被持有后,它將處于鎖定狀態。
如以下加鎖代碼:
public class SynchronizedToMonitorExample { public static void main(String[] args) { int count = 0; synchronized (SynchronizedToMonitorExample.class) { for (int i = 0; i < 10; i++) { count++; } } System.out.println(count); } }
當我們將上述代碼編譯成字節碼之后,它的內容是這樣的:
從上述結果可以看出,在 main 方法的執行中多個 monitorenter 和 monitorexit 的指令,由此可知 synchronized 是依賴 Monitor 監視器鎖實現的,而監視器鎖又是依賴操作系統的互斥鎖(Mutex Lock),互斥鎖在每次獲取和釋放鎖時,都會帶來用戶態和內核態的切換,這樣就增加了系統的性能開銷。
感謝各位的閱讀,以上就是“Java中的synchronized鎖膨脹機制怎么實現”的內容了,經過本文的學習后,相信大家對Java中的synchronized鎖膨脹機制怎么實現這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。