Java鎖事之偏向鎖

偏向鎖

當只有一個線程多次重復搶占鎖同一資源時,即使是輕量級鎖每次也至少需要兩次(加鎖、解鎖)CAS操作。而此場景經Hotspot統計是比較容易出現的。所以為了減少不必要的資源浪費,偏向鎖應運而生。

優點

  • 只需要執行一次CAS即可獲取鎖
  • 采用延遲釋放鎖策略
  • 鎖重入時,只需要判斷mark_word.threadId(關于對象頭的文章)是否為當前threadId即可

缺點

  • 總體上只針對第一個線程有效,新線程獲取鎖時,會導致鎖膨脹
  • 鎖膨脹時,會導致stop the world (STW)
  • 與原生hashcode()互斥,導致偏向鎖并非適應于所有的instance

如何獲取一把偏向鎖

前提

  1. JVM偏向鎖(-XX:+UseBiasedLocking)默認已開啟
  2. 確認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的值,若相等,則可以繼續占有鎖,否則說存在資源競爭,需要進行鎖膨脹。

鎖膨脹過程

  1. 所有要競爭鎖的線程到達安全區后,掛起對應線程。
  2. 遍歷原鎖持有線程的的調用棧的鎖記錄。將與被鎖instance相關的鎖記錄改為輕量級鎖相關的值。
  3. 更改被鎖instance的Mark Word,將其指向最早的鎖記錄。
  4. 釋放被掛起的相關線程。

偏向鎖的騷操作

批量重偏向

Mark Word 里與偏向鎖有關的信息除了threadId還有epoch,前面也提到epoch可以用來判斷是否可以重偏向。那么他是如何實現的呢?

批量重偏向是針對兩個線程串行共享instance資源場景(一個線程初始化instance傳遞給另一個線程)的優化。因為此時兩個線程并不存在競爭,所以第二個線程可以繼續使用偏向鎖。

實現前提

Hotspot 通過在klassOop(可以理解為類的原型,其結構與instance一致)。里添加一個epoch字段,當一個Klass實例化instance一個時,便會以klassOop為原型初始化,epoch便被初始化在了instance的Mark Word中。

重偏向過程

  1. 當JVM執行到一個全局安全點的時候,掛起所有線程。
  2. 給KlassOop.epoch + 1.
  3. 給所有被偏向鎖鎖住的instance的Mark Word中的epoch + 1。或者采用啟發式撤銷偏向鎖。
  4. 釋放線程。

啟發式撤銷偏向

啟發式撤銷偏是針對重偏向的一種預防式優化。其邏輯很簡單,通過設置一個閾值(k),一但epoch>k則執行撤銷偏向鎖操作,否者可以執行重偏向操作。

關于偏向鎖的JVM參數

啟用偏向鎖

-XX:+UseBiasedLocking

偏向鎖的延遲啟動時間

偏向鎖默認是在JVM啟動4s后再初始化偏向鎖,可用如下參數修改啟動時間,設為0則表示立即啟用。之所以這么設計是因為JVM啟動的時候,如果立即啟動偏向,有可能會因為線程競爭太激烈導致產生太多安全點掛起。
-XX:BiasedLockingStartupDelay=0

參考

  1. https://www.oracle.com/technetwork/java/javase/tech/biasedlocking-oopsla2006-preso-150106.pdf

  2. https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf

  3. http://ds.cs.ut.ee/courses/course-files/lockOptimizationHotSpot.pdf

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容