深入理解 synchronized

深入理解 synchronized

引言:synchronized的核心地位

在Java并發編程中,synchronized關鍵字是實現線程安全的基石。自JDK 1.0引入以來,它經歷了從"重量級鎖"到"自適應鎖"的進化,如今已成為兼顧安全性與性能的成熟方案。本文將從用法解析字節碼實現底層原理鎖升級機制JDK優化性能對比最佳實踐,全方位剖析synchronized的技術細節,結合OpenJDK源碼與實測數據,帶你徹底掌握這一并發利器。

一、synchronized的基本用法與語義

1.1 三種使用方式

synchronized可修飾方法或代碼塊,核心是通過對象鎖實現線程互斥。具體用法如下:

用法場景鎖對象字節碼實現示例代碼
修飾實例方法當前對象實例(this方法訪問標志ACC_SYNCHRONIZEDpublic synchronized void increment() { count++; }
修飾靜態方法類對象(Class實例)方法訪問標志ACC_SYNCHRONIZEDpublic static synchronized void staticIncrement() { staticCount++; }
修飾代碼塊顯式指定對象monitorenter/monitorexit指令synchronized (lockObj) { count++; }

關鍵語義

  • 互斥性:同一時刻只有一個線程能持有鎖,確保臨界區代碼串行執行。
  • 可見性:釋放鎖時,線程會將工作內存中的修改刷新到主內存;獲取鎖時,線程會失效本地緩存,從主內存加載最新值(通過內存屏障實現)。
  • 可重入性:線程可重復獲取已持有的鎖,通過_recursions計數器實現(見ObjectMonitor源碼)。

1.2 用法示例與字節碼分析

示例1:同步代碼塊
public class SyncBlockExample {private final Object lock = new Object();private int count = 0;public void increment() {synchronized (lock) { // 顯式指定lock為鎖對象count++;}}
}

字節碼反編譯javap -v SyncBlockExample.class):
同步代碼塊通過monitorenter(進入鎖)和monitorexit(釋放鎖)指令實現:

  public void increment();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: aload_01: getfield      #2                  // Field lock:Ljava/lang/Object;4: dup5: astore_1                          // 將鎖對象引用存入局部變量表6: monitorenter                      // 獲取鎖7: aload_08: dup9: getfield      #3                  // Field count:I12: iconst_113: iadd14: putfield      #3                  // Field count:I17: aload_118: monitorexit                       // 正常退出時釋放鎖19: goto          2722: astore_223: aload_124: monitorexit                       // 異常退出時釋放鎖25: aload_226: athrow27: return

注意:編譯器會生成兩個monitorexit,分別對應正常退出和異常退出,確保鎖必定釋放。

示例2:同步方法
public class SyncMethodExample {private int count = 0;public synchronized void increment() { // 實例方法鎖,鎖對象為thiscount++;}public static synchronized void staticIncrement() { // 靜態方法鎖,鎖對象為SyncMethodExample.classstaticCount++;}
}

字節碼特征:同步方法通過ACC_SYNCHRONIZED標志實現,無需顯式monitor指令:

  public synchronized void increment();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 同步方法標志Code:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field count:I5: iconst_16: iadd7: putfield      #2                  // Field count:I10: return

二、底層實現:對象頭與Monitor機制

2.1 對象頭與Mark Word

synchronized的實現依賴對象頭(Object Header)中的Mark Word存儲鎖狀態。對象頭由兩部分組成:

  • Mark Word:存儲對象運行時數據(哈希碼、GC年齡、鎖狀態等)。
  • Klass Pointer:指向類元數據的指針。

64位JVM Mark Word格式(不同鎖狀態下的存儲結構):

鎖狀態標志位存儲內容
無鎖01哈希碼(25bit)+ GC年齡(4bit)+ 是否偏向鎖(1bit=0)+ 標志位(2bit=01)
偏向鎖01偏向線程ID(54bit)+ Epoch(2bit)+ GC年齡(4bit)+ 是否偏向鎖(1bit=1)+ 標志位(2bit=01)
輕量級鎖00指向棧中鎖記錄(Lock Record)的指針(64bit)+ 標志位(2bit=00)
重量級鎖10指向ObjectMonitor對象的指針(64bit)+ 標志位(2bit=10)
GC標記11

工具推薦:使用org.openjdk.jol:jol-core查看對象頭,如ClassLayout.parseInstance(obj).toPrintable()

2.2 Monitor監視器鎖

重量級鎖的實現依賴ObjectMonitor(C++實現),每個對象關聯一個Monitor,用于管理線程競爭與等待。

ObjectMonitor核心結構(OpenJDK源碼objectMonitor.hpp):

class ObjectMonitor {
private:void* volatile _owner;       // 持有鎖的線程ObjectWaiter* volatile _WaitSet; // 等待隊列(調用wait()的線程)ObjectWaiter* volatile _EntryList; // 阻塞隊列(未獲取鎖的線程)int _recursions;             // 重入次數int _count;                  // 等待線程數// ...其他字段
};

工作流程

  1. 競爭鎖:線程通過CAS嘗試將_owner設為自身,成功則獲取鎖;失敗則進入_EntryList阻塞。
  2. 釋放鎖:線程退出同步塊時,將_owner設為null,喚醒_EntryList中的線程重新競爭。
  3. 等待/喚醒:調用wait()時,線程釋放鎖并進入_WaitSetnotify()將線程從_WaitSet移至_EntryList重新競爭。

三、鎖升級機制:從偏向鎖到重量級鎖

JDK 1.6引入鎖升級機制,根據競爭程度動態選擇鎖狀態(不可逆),平衡性能與安全性。

3.1 偏向鎖(Biased Locking)

設計目標:減少單線程重復獲取鎖的開銷。
實現原理

  • 首次獲取鎖時,通過CAS將線程ID記錄到Mark Word,設為偏向模式(標志位101)。
  • 后續同一線程訪問時,僅需比對線程ID,無需CAS操作。

撤銷條件:當其他線程嘗試競爭時,需等待全局安全點(STW),暫停持有線程,檢查狀態:

  • 若持有線程已結束,重置為無鎖狀態。
  • 若持有線程存活,升級為輕量級鎖。

JVM參數

  • -XX:+UseBiasedLocking(默認啟用,JDK 15后默認禁用)。
  • -XX:BiasedLockingStartupDelay=0(禁用啟動延遲)。

3.2 輕量級鎖(Lightweight Locking)

設計目標:應對多線程交替執行的輕度競爭。
實現步驟

  1. 創建鎖記錄:線程在棧幀中創建Lock Record,復制Mark Word(Displaced Mark Word)。
  2. CAS競爭鎖:通過CAS將Mark Word替換為指向Lock Record的指針(標志位00)。
  3. 自旋重試:競爭失敗時,線程自旋(空循環)嘗試獲取鎖,避免阻塞(自適應自旋:根據歷史成功率調整次數)。

升級條件

  • 自旋超過閾值(默認10次,JDK 1.7后自適應)。
  • 競爭線程數超過CPU核心數一半。

3.3 重量級鎖(Heavyweight Locking)

設計目標:應對高并發激烈競爭。
實現原理

  • Mark Word指向ObjectMonitor,未獲取鎖的線程進入_EntryList阻塞(操作系統級別的互斥鎖)。
  • 線程阻塞/喚醒涉及用戶態→內核態切換,開銷較大。

性能對比

鎖狀態獲取成本釋放成本適用場景
偏向鎖極低極低單線程重復訪問
輕量級鎖低(CAS)低(CAS)多線程交替執行
重量級鎖多線程同時競爭

四、JDK優化:從鎖消除到虛擬線程

4.1 鎖優化技術

鎖消除(Lock Elimination)

JIT編譯器通過逃逸分析,消除不可能存在競爭的鎖。例如:

public String concat(String a, String b) {StringBuffer sb = new StringBuffer(); // StringBuffer的append是同步方法sb.append(a).append(b);return sb.toString();
}
// 逃逸分析發現sb未逃逸,消除同步鎖
鎖粗化(Lock Coarsening)

合并連續的鎖申請,減少鎖競爭頻率:

for (int i = 0; i < 1000; i++) {synchronized (lock) { // 循環內頻繁加鎖,粗化為一次鎖申請count++;}
}
// 優化后:synchronized (lock) { for (...) { count++; } }
自適應自旋(Adaptive Spinning)

JVM根據歷史自旋成功率動態調整次數:

  • 若自旋成功,下次增加自旋次數(最大100次)。
  • 若自旋失敗,減少或省略自旋,避免CPU空轉。

4.2 JDK 17的虛擬線程支持

JDK 17通過JEP 491優化synchronized與虛擬線程(Virtual Threads)的兼容性,避免線程固定(Pinning):

  • 虛擬線程阻塞于synchronized時,JVM自動卸載載體線程(Carrier Thread),允許其他虛擬線程復用。
  • 實現原理:結合Continuation機制,在阻塞時保存棧幀,釋放載體線程。

性能提升:在高并發I/O場景,吞吐量提升30%+,避免傳統線程阻塞導致的資源浪費。

五、源碼深度剖析:ObjectMonitor關鍵方法

5.1 加鎖(enter方法)

void ATTR ObjectMonitor::enter(TRAPS) {Thread* Self = THREAD;void* cur = Atomic::cmpxchg_ptr(Self, &_owner, NULL); // CAS嘗試獲取鎖if (cur == NULL) { // 成功獲取鎖,_owner = Selfreturn;}if (cur == Self) { // 重入,_recursions++_recursions++;return;}// 競爭失敗,進入自旋或阻塞if (Knob_SpinEarly && TrySpin(Self) > 0) { // 自旋成功,獲取鎖return;}// 自旋失敗,進入_EntryList阻塞ThreadBlockInVM tbivm(Self);Self->set_current_pending_monitor(this);EnterI(Self); // 進入阻塞隊列
}

5.2 釋放鎖(exit方法)

void ATTR ObjectMonitor::exit(TRAPS) {if (_recursions != 0) { // 重入解鎖,_recursions--_recursions--;return;}// 喚醒_EntryList中的線程ObjectWaiter* w = NULL;w = _EntryList;if (w != NULL) {Atomic::cmpxchg_ptr(NULL, &_owner, Self); // 釋放鎖OrderAccess::fence();WakeupWaiter(w); // 喚醒線程return;}// 無等待線程,直接釋放Atomic::cmpxchg_ptr(NULL, &_owner, Self);
}

六、性能對比與最佳實踐

6.1 synchronized vs Lock(ReentrantLock)

特性synchronizedReentrantLock
實現層級JVM層面(關鍵字)JDK層面(接口)
鎖釋放自動釋放(異常/正常退出)手動釋放(需finally塊)
高級功能無(不可中斷、非公平)支持中斷、超時、公平鎖、條件變量
性能(低競爭)接近(偏向鎖/輕量級鎖)略高(CAS操作)
性能(高競爭)重量級鎖開銷大略優(隊列優化)

建議:簡單同步需求用synchronized(簡潔安全);需高級功能(如超時獲取)用ReentrantLock

6.2 生產環境最佳實踐

  1. 減小鎖粒度:同步代碼塊僅包裹臨界區,避免鎖范圍過大:

    // 反例:整個方法加鎖
    public synchronized void process() {readConfig(); // 無需同步的操作updateState(); // 需同步的臨界區
    }
    // 正例:僅臨界區加鎖
    public void process() {readConfig();synchronized (lock) {updateState();}
    }
    
  2. 避免嵌套鎖:減少死鎖風險,如必須嵌套,確保鎖順序一致。

  3. 禁用偏向鎖:高并發場景下,偏向鎖撤銷開銷大,通過-XX:-UseBiasedLocking禁用。

  4. 監控鎖狀態:使用jstack查看線程阻塞狀態,定位競爭熱點:

    jstack <pid> | grep -A 20 "BLOCKED"  # 查看阻塞線程
    
  5. JVM參數調優

    • -XX:PreBlockSpin=10:輕量級鎖自旋次數。
    • -XX:BiasedLockingBulkRebiasThreshold=20:批量重偏向閾值。
    • -XX:BiasedLockingBulkRevokeThreshold=40:批量撤銷閾值。

七、總結

synchronized從早期的重量級鎖進化為如今的自適應鎖機制,體現了JVM對性能的極致追求。其核心在于動態鎖升級(偏向鎖→輕量級鎖→重量級鎖),結合對象頭Mark Word與ObjectMonitor實現高效同步。在JDK 17中,通過對虛擬線程的支持,進一步提升了高并發場景下的可擴展性。

掌握synchronized的底層原理,不僅能寫出更高效的并發代碼,更能深入理解JVM的優化機制。在實際開發中,需結合業務場景選擇合適的鎖策略,平衡安全性與性能,避免過度同步或鎖競爭導致的性能瓶頸。

參考資料

  • OpenJDK源碼(hotspot/src/share/vm/runtime/objectMonitor.hpp)
  • 《深入理解Java虛擬機》(周志明)
  • JDK官方文檔(JEP 142、JEP 491)

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

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

相關文章

C語言字符串相關函數

C語言筆記內容提要數組字符串基本操作字符串相關函數綜合案例&#xff1a;學生成績管理系統數組字符串基本操作在用格式化說明符%s進行輸入輸出時&#xff0c;其輸入輸出項均為數組名。但在輸入時&#xff0c;相鄰兩個字符串之間要用空格分隔&#xff0c;系統將自動在字符串后加…

從零開始:用Python庫輕松搭建智能AI代理

為什么要關注AI代理&#xff1f; “Agentic AI”&#xff08;智能代理&#xff09;正在悄然改變我們的工作方式。想象一下&#xff0c;一個AI助手不僅能幫你查航班、訂機票&#xff0c;還能自動安排行程、發郵件、生成日報——就像一個效率極高的“虛擬助理”團隊。 對于測試工…

如何防止GitHub上的敏感信息被泄漏?

如大家所了解的&#xff0c;隨著GitHub的用戶越來越多&#xff0c;GitHub上的敏感信息被泄漏的問題也越來越嚴重。那么如何做&#xff0c;才能防止此類事情發生呢&#xff1f;這值得我們探討。移除并刪除敏感信息當我們發現了歷史 commit 中包含敏感信息后&#xff0c;第一步便…

船舶機械零件的深孔工藝及檢測方法 —— 激光頻率梳 3D 輪廓檢測

引言船舶機械零件中的深孔結構&#xff08;深徑比&#xff1e;15:1&#xff09;直接影響動力系統可靠性&#xff0c;如柴油機缸體深孔、推進軸系潤滑油孔等。此類深孔具有孔徑大&#xff08;φ10 - 50mm&#xff09;、深度深&#xff08;500 - 2000mm&#xff09;、表面質量要求…

論文Review Lidar 3DGS Splat-LOAM: Gaussian Splatting LiDAR Odometry and Mapping

基本信息 題目&#xff1a;Splat-LOAM: Gaussian Splatting LiDAR Odometry and Mapping 來源&#xff1a;ICCV 2025 學校&#xff1a;Sapienza University of Rome 是否開源&#xff1a;https://github.com/rvp-group/Splat-LOAM 摘要&#xff1a;純激光3DGS&#xff01;…

MYSQL:數據庫約束

文章目錄MYSQL&#xff1a;數據庫約束&#xff1a;為你的數據上把“安全鎖”1. 約束的類型概覽2. NOT NULL 非空約束3. DEFAULT 默認值約束4. UNIQUE 唯一約束5. PRIMARY KEY 主鍵約束5.1 自增主鍵 AUTO_INCREMENT5.3 復合主鍵6. FOREIGN KEY 外鍵約束7. CHECK 約束總結MYSQL&a…

網絡數據編碼技術及其應用場景的全面解析

網絡數據編碼技術全景圖?編碼類型??編碼原理??適用層??典型應用場景??優勢??缺陷??曼徹斯特編碼?電平跳變代表數據位&#xff08;高→低1&#xff0c;低→高0&#xff09;物理層10/100M以太網、RFID標簽自同步時鐘帶寬利用率僅50%?4B/5B編碼?4比特映射為5比特物…

RustDesk自建服務器完整部署指南:從零開始到成功連接。成功解決rustdesk報錯:未就緒,請檢查網絡連接

最近需要用到遠程工具解決用戶問題&#xff0c;todesk總是提示付費&#xff0c;干脆自己使用開源的。當然凡事都有代價。 話費了一個工作日的時間終于搞定了。 本文將詳細介紹如何從零開始部署RustDesk自建服務器&#xff0c;實現完全自主可控的遠程桌面解決方案。 踩坑 參考…

datasophon安裝doris問題排除記錄

datasophon安裝doris搞了好久才成功&#xff0c;特別記錄一下。 多災多難的安裝過程&#xff1a;FE安裝 首先&#xff0c;配置界面&#xff0c;要注意兩個參數一定要改成正確的網段&#xff0c;否則會被識別成127.0.0.1注意&#xff1a;兩個priority_networks 參數必須要改成你…

suricata新增Mysql告警規則處理

suricata新增Mysql告警規則處理協議解析后續處理內容新增規則規則解析關鍵字新增Setup用于初始化檢測項Free用于資源釋放AppLayerTxMatch用于協議解析完成后的規則檢測針對pcap文件進行檢測總結協議解析后續處理內容 經過Mysql協議解析處理流程 介紹&#xff0c;我們在suricat…

使用位運算優化 Vue.js 應用:高效狀態管理技巧

在 Vue.js 開發中&#xff0c;位運算&#xff08;Bitwise Operations&#xff09;是一種高效的工具&#xff0c;尤其適用于需要管理大量布爾狀態或優化性能的場景。位運算通過操作二進制位來實現狀態的存儲和檢查&#xff0c;相比傳統的數組或對象操作&#xff0c;內存占用更低…

【Java SE】Clonable接口和深拷貝

目錄 一.Clonable接口 實現步驟&#xff1a; 完整代碼&#xff1a; 二.深拷貝 實現步驟&#xff1a; 完整代碼&#xff1a; 淺拷貝與深拷貝的對比 使用場景建議 完 淺拷貝&#xff08;Shallow Copy&#xff09;和深拷貝&#xff08;Deep Copy&#xff09;是對象復制的兩…

accelerate 在Pycham中執行的設置方法

背景 使用 accelerate 進行分布式代碼訓練時&#xff0c;需要在pycharm中進行調試&#xff0c;此時需要在pycharm中運行。 終端執行命令 # *[Specify the config file path and the GPU devices to use] export CUDA_VISIBLE_DEVICES0# *[Specify the config file path] expo…

探索量子計算與法律理論的交叉領域

文章目錄 前言 一、引言 二、內容 (一)知識產權 (二)隱私與安全 (三)責任認定 (四)證據與證明 (五)法律推理與決策 三、結論 總結 前言 隨著量子計算技術的突破性發展,其引發的法律范式重構問題日益凸顯。烏茲別克斯坦學者伊索姆別克?阿卜迪哈基莫夫于2024年在《量…

js迭代器

文章目錄前言實現原理&#xff1a;調用迭代器自制迭代器前言 迭代器是 JSt 中一種特殊的對象&#xff0c;它提供了一種統一的、通用的方式遍歷個各種不同類型的數據結構。 可以遍歷的數據結構包括&#xff1a;數組、字符串、Set、Map 等可迭代對象。我們也可以自定義實現迭代器…

chainlink VRF中文教程(含mock),解決error: Arithmetic Underflow in createSubscription

?我使用的版本&#xff1a;chainlink-brownie-contracts version:1.3.0?1. Import 相關包 ,,, import {VRFConsumerBaseV2Plus} from "chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2PLUS.sol"; import {VRFV2PlusClient} from "chainlink/contract…

非線性優化框架CasADi工具箱求解最優控制問題OCP

CasADi是一個開源的Python/MATLAB庫&#xff0c;主要用于數值優化&#xff0c;特別是最優控制問題。它提供了一個易于使用的符號框架&#xff0c;用于處理和生成表達式&#xff0c;以及高效地生成導數信息。 https://web.casadi.org/get/https://web.casadi.org/get/ 所有OCP…

Type-C接口臺式顯示器:LDR6021引領新潮流

Type-C單口便攜顯示器LDR6021是市場上一種新興的顯示設備&#xff0c;以下是對其的詳細介紹一、主要特點 便攜性:LDR6021采用了Type-C接口作為數據傳輸和供電接口&#xff0c;這種設計使得它能夠與各種支持Type-C接口的設備無縫連接&#xff0c;如筆記本電腦、智能手機、平板電…

在翻譯語義相似度和會議摘要相似度評估任務中 ,分類任務 回歸任務 生成任務區別

在翻譯語義相似度&#xff08;Translation Semantic Similarity&#xff09;和會議摘要相似度&#xff08;Meeting Summary Similarity&#xff09;等任務中&#xff0c;通常會根據任務的目標和輸出形式&#xff0c;將其劃分為三類常見的任務類型&#xff1a;1. 分類任務定義&a…

UGUI 性能優化系列:第二篇——Canvas 與 UI 元素管理

UGUI 性能優化系列&#xff1a;第一篇——基礎優化與資源管理 UGUI 性能優化系列&#xff1a;第二篇——Canvas 與 UI 元素管理 UGUI 性能優化系列&#xff1a;第三篇——渲染與像素填充率優化 UGUI 性能優化系列&#xff1a;第四篇——高級優化與注意事項 在 UGUI 性能優化…