jvm 鎖升級機制

??Java 虛擬機(JVM)中的鎖升級機制(也稱為鎖膨脹)是 HotSpot 虛擬機為了優化 synchronized 關鍵字的性能而引入的一項重要技術。它的核心思想是:根據實際遇到的競爭激烈程度,動態地將鎖從開銷最小的狀態逐步升級到開銷更大的狀態,從而在無競爭或低競爭時減少鎖操作的開銷,而在高競爭時保證必要的互斥性和線程調度能力。

鎖的狀態主要有四種,升級路徑如下:

無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖

鎖只能升級(膨脹),不能降級(雖然理論上重量級鎖在競爭消失后可以降級,但HotSpot 實現中為了簡化,很少進行降級,尤其是從重量級鎖降級)。

1. 無鎖狀態

  • 初始狀態: 當一個對象剛被創建出來,且沒有任何線程嘗試獲取它的鎖時,它就處于無鎖狀態。
  • 特點: 沒有鎖的開銷。
  • 適用場景: 對象從未被同步訪問或只被單個線程訪問(無需同步)。

2. 偏向鎖

  • 設計目標: 優化同一個線程重復進入同步塊的場景(無實際競爭)。消除在無競爭情況下的同步原語開銷(如 CAS)。
  • 工作原理:
    1. 當第一個線程(T1)訪問同步塊時,JVM 會檢查對象頭中的 Mark Word。
    2. 如果當前是無鎖狀態,JVM 使用 CAS 操作嘗試將 Mark Word 中的線程 ID 設置為 T1 的 ID,并將鎖標志位設置為偏向模式。
    3. 如果 CAS 成功,T1 就持有了該對象的偏向鎖。后續只要 T1 進入這個同步塊,無需再進行任何同步操作(如 CAS 或鎖申請),只需簡單檢查對象頭中的線程 ID 是否還是自己。
  • 升級觸發:
    • 競爭出現: 當另一個線程(T2)嘗試獲取這個已經被偏向于 T1 的鎖時,偏向鎖就會失效。
    • JVM 會撤銷偏向鎖。撤銷過程需要等待持有偏向鎖的線程(T1)到達全局安全點(Safepoint),暫停 T1。
    • 檢查 T1 的狀態:
      • 如果 T1 已經退出同步塊(不再持有鎖),則將對象頭設置為無鎖狀態(或者根據情況嘗試重新偏向給 T2)。
      • 如果 T1 仍在同步塊中,則將鎖升級為輕量級鎖。JVM 會在 T1 的棧幀中創建一個鎖記錄(Lock Record),并將對象頭的 Mark Word 復制到該鎖記錄中(稱為 Displaced Mark Word),然后用 CAS 操作將對象頭指向 T1 棧幀中的鎖記錄地址(輕量級鎖狀態)。
  • 特點: 適用于只有一個線程反復訪問同步塊的場景。加鎖解鎖幾乎無額外開銷。撤銷偏向鎖有代價(需要暫停線程)。
  • 關閉偏向鎖: 由于偏向鎖在存在競爭時撤銷有開銷,且現代應用中共享數據競爭往往更常見,從 JDK 15 開始,偏向鎖默認被禁用(可通過 -XX:+UseBiasedLocking 開啟,但已不推薦)。

3. 輕量級鎖

  • 設計目標: 優化多個線程交替執行同步塊,但未發生真正并發競爭的場景(低競爭)。避免直接使用重量級鎖帶來的操作系統內核態切換的開銷。
  • 工作原理:
    1. 當線程嘗試獲取輕量級鎖時(可能是從無鎖升級而來,也可能是從偏向鎖撤銷升級而來),JVM 會在當前線程的棧幀中創建一個鎖記錄空間。
    2. 將對象頭的 Mark Word 復制到該鎖記錄中(稱為 Displaced Mark Word)。
    3. 然后線程嘗試使用 CAS 操作將對象頭中的 Mark Word 替換為指向該鎖記錄的指針。
      • 如果 CAS 成功,當前線程獲得輕量級鎖。鎖標志位變為 00
      • 如果 CAS 失敗(說明對象頭已被其他線程修改,即發生了競爭),當前線程會自旋(循環嘗試 CAS)一小段時間(自適應自旋)。
        • 如果在自旋期間成功獲取到鎖,則繼續執行。
        • 如果自旋結束仍未成功,或者自旋過程中競爭加劇(如又有新線程加入競爭),則鎖升級為重量級鎖
  • 解鎖過程: 使用 CAS 操作將 Displaced Mark Word 替換回對象頭。
    • 如果成功,解鎖完成。
    • 如果失敗(說明鎖已經膨脹為重量級鎖),則在釋放鎖的同時喚醒等待線程。
  • 特點: 使用 CAS 和自旋代替互斥量,避免了用戶態到內核態的切換,適用于線程阻塞時間非常短的場景(“忙等”)。自旋會消耗 CPU。如果鎖持有時間較長或競爭激烈,自旋會浪費 CPU,性能反而下降。
  • 升級觸發: CAS 失敗且自旋獲取鎖失敗(或自適應策略判斷競爭激烈)、調用 wait() 方法(因為 wait() 需要重量級鎖的監視器模型支持)。

4. 重量級鎖

  • 設計目標: 處理高并發、激烈競爭的場景。保證在任意時刻只有一個線程能進入同步塊。
  • 工作原理:
    • 當鎖升級到重量級鎖時,對象頭中的 Mark Word 會指向一個與對象關聯的監視器鎖(Monitor,也稱為管程或互斥鎖),這個結構通常存在于堆中。
    • 該 Monitor 內部維護了一個入口隊列(Entry Set) 和一個等待隊列(Wait Set)
    • 當一個線程嘗試獲取重量級鎖時:
      • 如果鎖可用(未被持有),則獲取成功,成為鎖的持有者。
      • 如果鎖已被其他線程持有,則當前線程會被阻塞(Park),并被操作系統掛起,放入入口隊列等待喚醒。這涉及到用戶態到內核態的切換(線程上下文切換),開銷最大。
    • 當持有鎖的線程釋放鎖時,它會喚醒入口隊列中的某個或所有等待線程(具體策略取決于實現,如公平/非公平),被喚醒的線程會重新嘗試獲取鎖。
  • 特點: 真正的互斥鎖。阻塞線程,不消耗 CPU 空轉。適用于競爭激烈或臨界區執行時間較長的場景。線程阻塞、喚醒、上下文切換開銷很大。
  • 升級觸發: 輕量級鎖自旋失敗、調用 Object.wait() 方法(強制升級)。

總結鎖升級機制

鎖狀態目標場景核心機制優點缺點升級觸發條件
無鎖無同步訪問-無開銷無法提供線程安全首次線程訪問
偏向鎖單線程重復訪問CAS 設置 Thread ID同一線程后續進入無開銷競爭時撤銷開銷大(需暫停線程)第二個線程嘗試獲取鎖
輕量級鎖低競爭(交替執行)CAS + 自旋 (棧鎖記錄)避免內核切換,開銷小自旋消耗 CPU,長時間競爭性能下降CAS失敗且自旋失敗 / 調用 wait()
重量級鎖高競爭操作系統 Monitor阻塞線程,不消耗 CPU 空轉阻塞/喚醒開銷大(內核切換)輕量級鎖升級失敗 / 顯式調用 wait()

關鍵點

  1. 自適應自旋: 輕量級鎖中的自旋次數不是固定的,JVM 會根據之前在該鎖上的自旋成功情況以及持有者的狀態,動態調整自旋時間(適應性自旋)。
  2. 鎖消除: JIT 編譯器在運行時,通過逃逸分析如果發現某個鎖對象不可能被其他線程訪問到(即不會發生競爭),它會將這個鎖操作完全消除掉。
  3. 鎖粗化: 如果 JVM 檢測到有一連串連續的操作都對同一個對象反復加鎖和解鎖(即使是在循環中),它可能會將加鎖的范圍擴大(粗化)到整個操作序列的外部,從而減少不必要的鎖申請/釋放次數。
  4. 偏向鎖的爭議與默認關閉: 在現代多核、高并發環境下,共享數據的競爭是常態,偏向鎖在首次獲得和撤銷時的開銷,以及它對應用程序啟動性能的影響(大量類初始化時偏向鎖操作)變得不可忽視。自 JDK 15 起,偏向鎖默認被禁用,輕量級鎖成為更常見的起點。-XX:-UseBiasedLocking 可以顯式關閉它(在 JDK 15+ 已經是默認行為)。
  5. hashCode() 的影響: 當調用一個未被覆蓋的 Object.hashCode()System.identityHashCode() 時,如果對象處于偏向鎖或輕量級鎖狀態,會導致鎖撤銷或升級,因為無鎖狀態下的 Mark Word 需要存儲哈希碼。

理解鎖升級的意義

鎖升級機制體現了 JVM 在性能正確性之間所做的精妙平衡:

  • 無競爭/低競爭: 最大程度減少開銷(偏向鎖、輕量級鎖)。
  • 高競爭: 保證正確性和線程調度的公平性/效率,接受較大的開銷(重量級鎖)。

了解鎖升級機制對于編寫高效、正確的并發程序至關重要。它解釋了為什么簡單的 synchronized 在低競爭場景下性能可以非常好,而在高競爭場景下性能會顯著下降。在需要極高并發性能的場景下,開發者可能會選擇更靈活、可優化的顯式鎖(如 ReentrantLock),但 synchronized 結合鎖升級在大多數場景下已經是非常高效且簡潔的選擇。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912976.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912976.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912976.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

金蝶云星空 (9.0版本) ERP的WebApi接口隨機出現SSLException

環境: java-1.8.0-openjdk-1.8.0.131 hutool-all 依賴, 5.8.25版本 項目背景: 發版上線,用的hutool工具類 HttpUtil.createPost() ,請求域名為https://xxx.ik3cloud.com/k3cloud 的金蝶ERP webapi接口 問題&#xff1…

用java,把12.25.pdf從最后一個點分割,得到pdf

要在Java中從文件名 12.25.pdf 的最后一個點(.)分割文件名和擴展名,可以使用 String 類的 lastIndexOf() 和 substring() 方法。以下是一個示例代碼: public class FileNameSplitter {public static void main(String[] args) {St…

UE5 重新編譯插件版本

打開要轉換的UE的安裝目錄,一直找到這個文件 不要雙擊,在地址欄里輸入cmd打開命令行,輸入如下指令 RunUAT.bat BuildPlugin -plugin"E:\OldPlugin\chatbot5.3\chatbot\chatbot.uplugin" -package"E:\NewPlugin"-plugin…

Linux下的調試器-gdb(16)

文章目錄 預備知識(9-2.30.00)快速認識 gdbgdb 的命令1. 更換成 cgdb2. 打和去除斷點3. 逐語句與逐過程4. 使能(激活)斷點 調試思想1. 找到問題(找到問題所在的區域)2. 查看代碼的上下文 補充調試技巧1. wa…

李宏毅NLP-7-計算分數和訓練和測試

文章目錄 分數計算訓練測試 分數計算 插入式序列生成模型的概率計算邏輯,核心是將 “生成序列 h 的過程” 拆解為一系列插入操作,并通過步驟概率的乘積計算總概率 P ( h ∣ X ) P(h∣X) P(h∣X)。以下從 模型框架、步驟分解、概率計算 三個層面解析&…

Python字符與ASCII轉換方法

在Python中,可以使用內置函數 ord() 和 chr() 來轉換字符和ASCII碼: ?獲取字符的ASCII碼? - 用 ord() ascii_code ord(A) # 返回 65 ?將ASCII碼轉為字符? - 用 chr() character chr(65) # 返回 A 示例: # 打印字母A-Z的ASCII碼…

[IMX][UBoot] 10.啟動流程 (6) - bootz 命令啟動 Linux

文章鏈接 UBoot 啟動流程 (1) - 基本流程 UBoot 啟動流程 (2) - 平臺前期初始化階段 - board_init_f UBoot 啟動流程 (3) - UBoot 程序重定位 - relocate_code UBoot 啟動流程 (4) - 平臺后期初始化階段 - board_init_r UBoot 啟動流程 (5) - UBoot 運行階段 - main_loop …

TCP 三次握手協商 MSS 前,如何確定 MSS 值(結合 Linux 內核源碼分析)

文章目錄 一、SYN總結影響 SYN MSS 的因素 二、SYNACK總結影響 SYNACK MSS 的因素 結合 Linux 內核源碼 一、SYN 總結影響 SYN MSS 的因素 套接字選項 TCP_MAXSEG路由選項 advmss出口 MTU 減去 40(TCP 和 IP 的固定首部大小)IPV4_MAX_PMTU - 40(同上) 二、SYNACK 總結影響 SY…

面試150 矩陣置0

思路 我們使用兩個標記集合,分別記錄當矩陣的元素為0的時候的橫、縱坐標。然后在對矩陣元素進行遍歷,如果所在行或者所在列的索引在集合中,對應的矩陣元素修改為0即可 class Solution:def setZeroes(self, matrix: List[List[int]]) -> N…

Element UI 完整使用實戰示例

以下是 Element UI 的完整使用實戰示例,涵蓋從環境搭建、基礎組件使用到項目實戰的全流程,結合多個實際場景和代碼示例: 一、環境搭建與基礎配置 1. 安裝 Element UI 通過 npm 或 yarn 安裝: npm install element-ui --save # …

C# 線程同步(一)同步概念介紹

目錄 1.阻塞(Blocking) 2.阻塞 VS 輪詢 3.線程狀態 到目前為止,我們已經闡述了如何在線程上啟動任務、配置線程以及實現雙向數據傳遞。同時,我們也說明了局部變量是線程私有的,而引用可以通過共享字段在線程間傳遞以…

解決leetcode第3588題.找到最大三角形面積

3588.找到最大三角形面積難度:中等問題描述:給你一個二維數組coords,大小為nx2,表示一個無限笛卡爾平面上n個點的坐標。找出一個最大三角形的兩倍面積,其中三角形的三個頂點來自coords中的任意三個點,并且該…

WIFI 安全測試記錄

之前為實訓課特意買的無線網卡沒用上,但是我怎么可能讓他荒廢。所以用了幾個下午,淺學了WiFi,當然沒找到什么好教材,自己摸索著學的很基礎,主要是當練習了,特此把我此前學習…WiFi密碼實踐過程寫上來。 省流…

android14設置--網絡--Internet副標題修改

收銀機訂制項目 插SIM卡,設備使用數據流量時,設置–網絡–Internet副標題顯示對應SIM卡運營商名稱,客戶要求修改這時的名稱(注意圖標也要同步修改) packages\apps\Settings\src\com\android\settings\network\InternetPreferenceController.j…

Web3區塊鏈有哪些崗位?

Web3區塊鏈領域的崗位豐富多樣,涵蓋技術開發、產品管理、運營、商務等多個方面,以下是具體介紹: - 技術開發類: - 智能合約開發工程師:負責編寫、審計和優化智能合約,常見于DeFi開發,包括抵押…

解決 Spring Boot 對 Elasticsearch 字段沒有小駝峰映射的問題

場景重現在使用 MyBatis/Mybatis-Plus 框架對 MySQL 操作時習慣了字段名小駝峰映射,然而在操作 Elasticsearch 時發現字段名沒有小駝峰映射。解決方法1. 使用 ObjectMapper 手動轉換: 這是最直接也最常用的方法。 在 Spring Boot 應用中使用 ObjectMappe…

Error:Cannot find module ‘chokidar‘

錯誤復現 在vue開發中,出現報錯:Error:Cannot find module ‘chokidar’ 原因 缺包導致 解決方案 直接安裝依賴包 npm install chokidar依舊無效,刪除node_modules重新安裝 rm -rf node_modules npm i

Spring AI 向量數據庫詳解與 RAG 簡單實戰項目

一、什么是向量數據庫? 向量數據庫用于存儲、檢索稠密語義向量(Embedding),是構建 RAG(檢索增強生成)系統的核心組件。它支持近似最近鄰搜索(ANN),可根據語義相似度找出…

【RK3568+PG2L50H開發板實驗例程】Linux部分/FPGA FSPI 通信案例

本原創文章由深圳市小眼睛科技有限公司創作,版權歸本公司所有,如需轉載,需授權并注明出處(www.meyesemi.com) 1. 簡介 本案例旨在 ARM端運行 Linux系統,基通過 FSPI測試。 2. ARM端和 FPGA端通信流程 (1)ARM端實現SP…

github如何創建一個自己的倉庫保姆級教程

文章目錄 準備階段(github官網)添加ssh公鑰添加token創建倉庫 本地設置本地代理創建倉庫添加文件到倉庫進行提交 準備階段(github官網) 添加ssh公鑰 創建SSH KEY。先看一下你C盤用戶目錄下有沒有.ssh目錄,有的話看下里面有沒有id_rsa和id_rsa.pub這兩個文件&#…