《Java并发编程的艺术》之synchronized的底层实现原理
在學習鎖優化時,對象頭(Mark Word) 是必不可缺的一環,因為synchronized 用的鎖是存在對象頭里的。32位的虛擬機上對象頭占64位(8字節),64位的虛擬機上對象頭占128位(16字節)[^objectHead];而不同的類型,對象頭的布局不太一樣:
- 數組類型:Mark Word、Class Metadata Address、Array Length
- 普通類型:Mark Word、Class Metadata Address
Mark Word 表示對象的HashCode 或 鎖信息
Class Metadata Address 表示對象的數據類型在方法區對應的地址
Array Length 表示數組的長度(只在對象是數組的情況下才會存在)
對象頭的默認表示應該如下所示
| 無狀態鎖 | 對象的hashcode | 對象分代年齡 | 0 | 01 |
具體的對象內存布局看這篇文章
而根據JVM的設置1,具體分配時又會有不同的情況,如下所示
當關閉了偏向鎖的設置,那么就會走左邊的流程;反之則走右邊的流程。
偏向鎖
由于大多數情況下,鎖大多都不處于多線程競爭狀態,而且總是由同一個線程獲取,所以JVM在1.6之后加入了偏向鎖 和 輕量鎖 ,如今總共由4種鎖狀態:無狀態鎖、偏向鎖、輕量鎖、重量鎖。隨著線程競爭的提升,鎖會逐漸升級(無法降級)。
偏向鎖在沒有競爭的情況下可以提高同步的性能,這方面主要體現在偏向鎖只需要進行一次CAS而輕量鎖需要兩次。它是一個需要權衡利弊的選擇,它不是在任何情況下都對程序有利的。如果競爭很多,那么撤銷偏向鎖的過程就會成為性能瓶頸。
當偏向鎖可用時,初始化的對象頭分配如下所示
| 偏向鎖 | 線程ID | epoch | 對象分代年齡 | 1 | 01 |
加鎖過程
鎖撤銷
由于偏向鎖使用了一種直到競爭發生時才會釋放的機制,所以當其他線程競爭偏向鎖時,持有偏向鎖的線程才會去釋放鎖。
輕量鎖
加鎖過程
注意:輕量鎖會一直保持,喚醒總是發生在輕量鎖解鎖的時候,因為加鎖的時候已經成功CAS操作;而CAS失敗的線程,會立即鎖膨脹,并阻塞等待喚醒。
鎖釋放過程
總結
本章是對synchronized 在JVM里的各種等級及升級的流程進行了講解,其中主要是通過控制對象頭的一些狀態來控制鎖的等級。偏向鎖通過標記Thread ID 來表示,當前對象已經被對應線程占用;輕量鎖則替換Mark Word 為 Lock Record 地址 來表示當前對象被對應線程占用。無論是哪種鎖,在不同的場景下有不同的需求,可以參考以下表格做出選擇
偏向鎖:
- 優點:加鎖和解鎖不需要額外消耗,和執行非同步方法相比,僅存在納秒級的差距
- 缺點:如果線程間存在競爭,會帶來額外開銷(偏向鎖的撤銷)
- 適用場景: 適用于只有一個線程訪問同步塊的場景
輕量鎖:
- 優點: 競爭的線程不會造成阻塞,提高了程序的響應速度
- 缺點: 如果始終得不到鎖,使用自旋會消耗CPU
- 適用場景: 追求相應實踐,同步塊執行速度非常快
重量鎖:
- 優點: 線程競爭不使用自選,不會消耗CPU
- 缺點: 線程阻塞,響應時間緩慢
- 適用場景: 追求吞吐量,同步塊執行速度較慢
這個是網上找到的關于鎖撤銷、膨脹等操作的總流程
關于偏向鎖的相關JVM設置:-XXBiasedLockingStartupDelay=0表示啟動程序幾秒鐘后激活偏向鎖-XXUseBiasedLocking=false表示關閉偏向鎖(確定會發生競爭時可以這么設置)?
轉載于:https://www.cnblogs.com/codeleven/p/10963092.html
總結
以上是生活随笔為你收集整理的《Java并发编程的艺术》之synchronized的底层实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【光学】基于matlab色散曲线拟合【含
- 下一篇: jdbc连接mysql传参_将参数传递给