偏向鎖
當只有一個線程多次重復搶占鎖同一資源時,即使是輕量級鎖每次也至少需要兩次(加鎖、解鎖)CAS操作。而此場景經Hotspot統計是比較容易出現的。所以為了減少不必要的資源浪費,偏向鎖應運而生。
優點
- 只需要執行一次CAS即可獲取鎖
- 采用延遲釋放鎖策略
- 鎖重入時,只需要判斷mark_word.threadId(關于對象頭的文章)是否為當前threadId即可
缺點
- 總體上只針對第一個線程有效,新線程獲取鎖時,會導致鎖膨脹
- 鎖膨脹時,會導致stop the world (STW)
- 與原生hashcode()互斥,導致偏向鎖并非適應于所有的instance
如何獲取一把偏向鎖
前提
- JVM偏向鎖(-XX:+UseBiasedLocking)默認已開啟
- 確認instance可用偏向鎖可用,即mark word 鎖狀態標記位為 01
分支條件
匿名偏向
若instance處于匿名偏向狀態(即初始狀態,threadId為null),則執行CAS操作,將當前線程的id賦值到對象的mark word中,若成功則獲取偏向鎖成功,否則說明有多個線程競爭資源需要進行鎖膨脹。
重偏向
即instance的Mark Word里的epoch與與klassOop.epoch標志位不一致時,表示此instance可被重偏向,此時新線程可以執行CAS操作進行鎖搶占。
已偏向
即threadId已存在,且instance的epoch有效(與klassOop.epoch相等)此時instance處于已偏向狀態。此時需比較當前threadId與mark_word.threadId的值,若相等,則可以繼續占有鎖,否則說存在資源競爭,需要進行鎖膨脹。
鎖膨脹過程
- 所有要競爭鎖的線程到達安全區后,掛起對應線程。
- 遍歷原鎖持有線程的的調用棧的鎖記錄。將與被鎖instance相關的鎖記錄改為輕量級鎖相關的值。
- 更改被鎖instance的Mark Word,將其指向最早的鎖記錄。
- 釋放被掛起的相關線程。
偏向鎖的騷操作
批量重偏向
Mark Word 里與偏向鎖有關的信息除了threadId還有epoch,前面也提到epoch可以用來判斷是否可以重偏向。那么他是如何實現的呢?
批量重偏向是針對兩個線程串行共享instance資源場景(一個線程初始化instance傳遞給另一個線程)的優化。因為此時兩個線程并不存在競爭,所以第二個線程可以繼續使用偏向鎖。
實現前提
Hotspot 通過在klassOop(可以理解為類的原型,其結構與instance一致)。里添加一個epoch字段,當一個Klass實例化instance一個時,便會以klassOop為原型初始化,epoch便被初始化在了instance的Mark Word中。
重偏向過程
- 當JVM執行到一個全局安全點的時候,掛起所有線程。
- 給KlassOop.epoch + 1.
- 給所有被偏向鎖鎖住的instance的Mark Word中的epoch + 1。或者采用啟發式撤銷偏向鎖。
- 釋放線程。
啟發式撤銷偏向
啟發式撤銷偏是針對重偏向的一種預防式優化。其邏輯很簡單,通過設置一個閾值(k),一但epoch>k則執行撤銷偏向鎖操作,否者可以執行重偏向操作。
關于偏向鎖的JVM參數
啟用偏向鎖
-XX:+UseBiasedLocking
偏向鎖的延遲啟動時間
偏向鎖默認是在JVM啟動4s后再初始化偏向鎖,可用如下參數修改啟動時間,設為0則表示立即啟用。之所以這么設計是因為JVM啟動的時候,如果立即啟動偏向,有可能會因為線程競爭太激烈導致產生太多安全點掛起。
-XX:BiasedLockingStartupDelay=0