https://blog.csdn.net/lengxiao1993/article/details/81568130
Java SE1.6 為了改善性能, 使得 JVM 會根據競爭情況, 使用如下 3 種不同的鎖機制
- 偏向鎖(Biased Lock )
- 輕量級鎖( Lightweight Lock)
- 重量級鎖(Heavyweight Lock)
上述這三種機制的切換是根據競爭激烈程度進行的, 在幾乎無競爭的條件下, 會使用偏向鎖, 在輕度競爭的條件下, 會由偏向鎖升級為輕量級鎖, 在重度競爭的情況下, 會升級到重量級鎖。
1.偏向鎖
注意 JVM 提供了關閉偏向鎖的機制, JVM 啟動命令指定如下參數即可-XX:-UseBiasedLocking
根據偏斜鎖機制是否打開, 對象 MarkWord 狀態以不同方式轉換的過程
無鎖 -> 偏向鎖
偏向鎖的獲取方式是將對象頭的 MarkWord 部分中, 標記上線程ID, 以表示哪一個線程獲得了偏向鎖。
1.如果為可偏向狀態, 則嘗試用 CAS 操作, 將自己的線程 ID 寫入MarkWord
- 如果 CAS 操作成功(狀態轉變為下圖), 則認為已經獲取到該對象的偏向鎖, 執行同步塊代碼 。
- 補充: 一個線程在執行完同步代碼塊以后, 並不會嘗試將 MarkWord 中的 thread ID 賦回原值 。這樣做的好處是: 如果該線程需要再次對這個對象加鎖,而這個對象之前一直沒有被其他線程嘗試獲取過鎖,依舊停留在可偏向的狀態下, 即可在不修改對象頭的情況下, 直接認為偏向成功。
如果 CAS 操作失敗, 則說明, 有另外一個線程 Thread B 搶先獲取了偏向鎖。 這種狀態說明該對象的競爭比較激烈, 此時需要撤銷 Thread B 獲得的偏向鎖,將 Thread B 持有的鎖升級為輕量級鎖。 該操作需要等待全局安全點 JVM safepoint ( 此時間點, 沒有線程在執行字節碼)。
2.如果是已偏向狀態, 則檢測 MarkWord 中存儲的 thread ID 是否等於當前 thread ID 。
- 如果相等, 則證明本線程已經獲取到偏向鎖, 可以直接繼續執行同步代碼塊
如果不等, 則證明該對象目前偏向於其他線程, 需要撤銷偏向鎖
偏向鎖的撤銷(Revoke)
如上文提到的, 偏向鎖的撤銷(Revoke) 操作並不是將對象恢復到無鎖可偏向的狀態, 而是在偏向鎖的獲取過程中, 發現了競爭時, 直接將一個被偏向的對象“升級到” 被加了輕量級鎖的狀態。 這個操作的具體完成方式如下:
- 在偏向鎖 CAS 更新操作失敗以后, 等待到達全局安全點。
通過 MarkWord 中已經存在的 Thread Id 找到成功獲取了偏向鎖的那個線程, 然后在該線程的棧幀中補充上輕量級加鎖時, 會保存的鎖記錄(Lock Record), 然后將被獲取了偏向鎖對象的 MarkWord 更新為指向這條鎖記錄的指針。
偏向鎖 -> 輕量級鎖
從之前的描述中可以看到, 存在超過一個線程競爭某一個對象時, 會發生偏向鎖的撤銷操作。 有趣的是, 偏向鎖撤銷后, 對象可能處於兩種狀態。
一種是不可偏向的無鎖狀態, 如下圖(之所以不允許偏向, 是因為已經檢測到了多於一個線程的競爭, 升級到了輕量級鎖的機制)
另一種是不可偏向的已鎖 ( 輕量級鎖) 狀態
之所以會出現上述兩種狀態, 是因為偏向鎖不存在解鎖的操作, 只有撤銷操作。 觸發撤銷操作時:
- 原來已經獲取了偏向鎖的線程可能已經執行完了同步代碼塊, 使得對象處於 “閑置狀態”,相當於原有的偏向鎖已經過期無效了。此時該對象就應該被直接轉換為不可偏向的無鎖狀態。
原來已經獲取了偏向鎖的線程也可能尚未執行完同步代碼塊, 偏向鎖依舊有效, 此時對象就應該被轉換為被輕量級加鎖的狀態
輕量級加鎖過程:
- 首先根據標志位判斷出對象狀態處於不可偏向的無鎖狀態( 如下圖)
- 在當前線程的棧楨(Stack Frame)中創建用於存儲鎖記錄(lock record)的空間,並將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。如果在此過程中發現,
- 然后線程嘗試使用 CAS 操作將對象頭中的 Mark Word 替換為指向鎖記錄的指針。
-
- 如果成功,當前線程獲得鎖
- 如果失敗,表示該對象已經被加鎖了, 先進行自旋操作, 再次嘗試 CAS 爭搶, 如果仍未爭搶到, 則進一步升級鎖至重量級鎖。