首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

深度解析 Java synchronized 關鍵字:鎖機制、內存語義與最佳實踐

     編輯:Java 程式設計入門

核心結論先行

synchronized 是 Java 並發編程的基石,本質是監視器鎖。它保證三大特性:原子性、可見性、有序性。JDK 1.6 之後經過大量優化(偏向鎖、輕量級鎖、鎖消除、鎖粗化、自旋鎖、適應性自旋),性能已非早期「重量級」可比,應優先考慮使用,而非盲目追求 `Lock` 或無鎖結構。

兩種基本用法

1. 同步方法


// 實例方法:鎖為 this
public synchronized void instanceMethod() { ... }

// 靜態方法:鎖為 Class 對象
public static synchronized void staticMethod() { ... }

等價於:


public void instanceMethod() {
    synchronized(this) { ... }
}

public static void staticMethod() {
    synchronized(MyClass.class) { ... }
}

2. 同步代碼塊


// 任意對象作為鎖
synchronized(lockObject) {
    // 臨界區代碼
}

靈活性更高:可精確控制鎖範圍(縮小臨界區提高併發度)、指定任意鎖對象(實現多把鎖分離)。

鎖對象的核心規則(必須牢記)

代碼形式鎖對象作用範圍
synchronized 實例方法當前實例 (this)同一實例的所有同步方法互斥
synchronized 靜態方法當前類 Class 對象所有實例的該靜態方法互斥
synchronized(this)當前實例 (this)同一實例的該塊代碼互斥
synchronized(ClassName.class)Class 對象所有實例的該塊代碼互斥
synchronized(privateLock)自定義私有對象僅控制訪問該對象的代碼塊

關鍵推論

  • 實例鎖與類鎖互不干擾:同一對象調用同步實例方法與同步靜態方法不會互斥,因為鎖對象不同(`this` vs `Class`)。
  • 不同實例互不干擾:`obj1.syncMethod()` 與 `obj2.syncMethod()` 可並發,鎖住的是各自的 `this`。
  • 同步塊鎖對象不可為 null:會拋 `NullPointerException`。

內存語義:happens-before 保證

synchronized 的語義由 JMM (Java Memory Model) 定義:

  1. 解鎖 (退出同步塊/方法) → happens-before → 後續對同一鎖的加鎖 (進入同步塊/方法)。
  2. 這意味著:線程 A 在同步塊內對共享變量的寫入,對後續獲得同一鎖的線程 B 可見;且指令重排不會越過同步塊邊界。

JDK 1.6+ 鎖優化全景圖

鎖狀態升級路徑(不可逆)


無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
        (單線程)   (低併發)     (高併發)
鎖類型適用場景核心機制優缺點
偏向鎖無併發、單線程重複獲取CAS 設置 ThreadID 到 Mark Word,後續進出無 CAS極低開銷;有併發時撤銷成本高
輕量級鎖低併發、交替執行CAS 將鎖記錄指針存入 Mark Word,自旋等待無阻塞;自旋消耗 CPU
重量級鎖高併發、長耗時臨界區OS 互斥量,線程阻塞掛起公平調度;上下文切換開銷大

其他優化

  • 鎖消除:JIT 編譯器逃逸分析發現鎖對象無併發訪問,直接刪除鎖操作。
  • 鎖粗化:連續多次對同一鎖加解鎖(如循環內),擴展為一次大鎖。
  • 適應性自旋:自旋次數由前次成功經驗動態調整。

實戰避坑指南

1. 鎖對象選擇原則


// ✅ 好:私有 final 對象,不可被外部竄改
private final Object lock = new Object();

// ✅ 好:零長度 byte 數組,創建開銷最小(3 字節碼 vs Object 7 字節碼)
private final byte[] lock = new byte[0];

// ❌ 坏:String 常量池導致不同類共享同一把鎖
synchronized("lock") { ... }

// ❌ 坏:基本類型包裝器自動拆箱裝箱導致鎖對象變化
Integer count = 0;
synchronized(count) { count++; } // 每次鎖住新對象!

// ❌ 坏:this 可能被外部代碼 synchronized,導致死鎖風險
// 建議:私有鎖封裝內部狀態,公開鎖僅用於協作

2. 死鎖預防

  • 固定鎖獲取順序(如按 `System.identityHashCode` 排序)。
  • 使用 `tryLock(timeout)` 替代無限等待(需 `ReentrantLock`)。
  • 避免在持有鎖時調用外部方法/回調。

3. 臨界區最小化


// ❌ 粗粒度:整個方法加鎖,包含 IO、睡眠
public synchronized void process() { 
    saveToDb(); // 耗時操作
    sendEmail(); 
}

// ✅ 細粒度:僅保護共享狀態讀寫
public void process() {
    Data snapshot;
    synchronized(lock) { snapshot = copyData(); } // 快速複製
    saveToDb(snapshot); // 耗時操作移出鎖
    sendEmail(snapshot);
}

4. 線程安全集合優先

不要手寫 `synchronizedMap`,直接用:

  • `Collections.synchronizedMap(new HashMap<>())` —— 方法級鎖,迭代需手動同步。
  • `ConcurrentHashMap` —— 分段鎖/CAS,高併發首選,弱一致性迭代器。
  • `CopyOnWriteArrayList` —— 讀多寫少場景。

對比:synchronized vs ReentrantLock

維度synchronizedReentrantLock
API 層級關鍵字/JVM 內置類庫級 (AQS)
公平性非公平可選公平/非公平
條件變量wait/notify/notifyAll多個 Condition
可中斷不可中斷lockInterruptibly()
超時獲取不支持tryLock(timeout)
性能 (高併發)優化後極具競爭力略優/持平
調試支持JVM 內置監控需代碼埋點

建議:優先用 `synchronized`(代碼簡潔、JVM 優化紅利、工具支持好);需公平鎖、條件變量、可中斷、超時獲取等高級特性時,再用 `ReentrantLock`。

總結

`synchronized` 看似簡單,實則蘊含「鎖對象選擇、內存語義、JVM 優化機制、工程避坑」四大維度深度。掌握私有 final 鎖對象、臨界區最小化、理解鎖升級與消除、善用線程安全集合,是寫出高性能、無 Bug 並發代碼的關鍵。記住:過早優化是萬惡之源,先正確,再快速,最後才是併發優化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved