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

Java synchronized 關鍵字深度解析:鎖機制、同步塊/方法、對象鎖 vs 類鎖、最佳實踐

     編輯:Java 綜合教學

一、核心概念:synchronized 鎖的是「對象」,不是代碼

  • 每個 Java 對象天生擁有一把內部鎖 (Monitor Lock / Intrinsic Lock)。
  • synchronized 作用於實例方法 → 鎖 this (當前實例)。
  • synchronized 作用於靜態方法 → 鎖 ClassName.class (類對象)。
  • synchronized(obj) 作用於同步塊 → 鎖 obj (任意對象引用)。
  • 同一把鎖同一時間只能被一個執行緒持有;其他執行緒嘗試獲取會阻塞 (BLOCKED) 直到釋放。

二、兩種基本用法

用法語法鎖對象適用場景
同步方法public synchronized void method()實例方法鎖 this;靜態方法鎖 Class.class整個方法需原子性,程式碼簡潔
同步塊synchronized(lockObj) { ... }括號內運算式結果對象縮小鎖範圍、提升並發、指定特定鎖對象

三、經典行為驗證 (五大規則)

假設類 Demo 有實例 d1, d2:

  1. 同一實例,同步塊/方法互斥:
    執行緒 A 進入 d1.syncBlock(),執行緒 B 進入 d1.syncMethod() → B 阻塞,因兩者鎖同一把 d1。
  2. 同一實例,同步 vs 非同步並行:
    執行緒 A 進入 d1.syncBlock(),執行緒 B 進入 d1.nonSyncMethod() → 並行執行,非同步方法不獲取鎖。
  3. 不同實例,同步方法並行:
    執行緒 A 進入 d1.syncMethod(),執行緒 B 進入 d2.syncMethod() → 並行執行,鎖分別是 d1、d2。
  4. 靜態同步 vs 實例同步並行:
    執行緒 A 進入 Demo.staticSync() (鎖 Demo.class),執行緒 B 進入 d1.instanceSync() (鎖 d1) → 並行執行,鎖不同。
  5. 指定任意對象為鎖:
    synchronized(sharedLock) { ... } 可讓不同實例共享同一把鎖,實現跨實例互斥。

四、程式碼驗證範例

public class SyncDemo {
    private final Object lock = new Object(); // 專用鎖對象 (推薦 private final)
    private int count = 0;

    // 1. 實例同步方法
    public synchronized void incSyncMethod() { count++; }

    // 2. 靜態同步方法
    public static synchronized void staticSync() { /* ... */ }

    // 3. 同步塊 (鎖 this)
    public void incSyncBlockThis() { synchronized(this) { count++; } }

    // 4. 同步塊 (鎖專用對象) - 推薦做法
    public void incSyncBlockPrivate() { synchronized(lock) { count++; } }

    // 5. 非同步方法
    public void nonSync() { count++; } // 線程不安全!
}

五、進階主題

1. 重入鎖

同一執行緒可重複獲取同一把鎖 (計數器 +1),避免自己死鎖自己。
例:同步方法呼叫另一同步方法 (同鎖) 正常執行。

2. 鎖升級與優化 (JDK 6+)

  • 偏向鎖:無競爭時,鎖偏向首個獲取執行緒,CAS 修改 Mark Word,極低開銷。
  • 輕量級鎖:有競爭但無阻塞,CAS 自旋獲取,避免核心態切換。
  • 重量級鎖:競爭激烈,執行緒阻塞進入 OS 互斥量。
  • 鎖消除:JIT 編譯器偵測到鎖對象無逃逸 (如區域變數),直接移除同步。
  • 鎖粗化:連續多次對同一鎖操作 (如循環內 synchronized),合併為單一大鎖。

3. wait/notify/notifyAll 機制

僅能在 synchronized 同步塊/方法內呼叫,釋放鎖並進入等待隊列。
現代替代:java.util.concurrent.locks.Condition (配合 ReentrantLock) 功能更強、語義更清晰。

4. 死鎖預防

  • 固定鎖獲取順序 (如按 hashCode 或業務 ID 排序)。
  • 嘗試獲取鎖設定超時 (ReentrantLock.tryLock(timeout))。
  • 避免在持有鎖時呼叫外部/未知代碼 (回調、監聽器)。

六、最佳實踐備忘錄

  1. 優先使用 java.util.concurrent 高層工具:ReentrantLock, ConcurrentHashMap, AtomicInteger, CountDownLatch, CompletableFuture 等,比手寫 synchronized 更安全、效能更優。
  2. 若必須用 synchronized:
    • 鎖對象用 private final Object lock = new Object(); (不可變、不可被外部竊取)。
    • 縮小同步塊範圍:只包裹共享狀態讀寫,耗時 I/O/計算移至同步塊外。
    • 避免鎖 String、Integer 等常量池/快取對象 (可能被其他代碼意外共享)。
    • 永不鎖 this 於公開 API (外部可竊取鎖導致活鎖/死鎖)。
  3. 區分對象鎖 vs 類鎖:靜態資料用 synchronized(ClassName.class) 或 static synchronized;實例資料用實例鎖。
  4. 讀多寫少場景:改用 ReentrantReadWriteLock 或 StampedLock (JDK 8+)。
  5. 啟用 JIT 優化:熱點代碼會自動進行鎖消除/粗化/偏向,保持代碼簡單讓編譯器發揮作用。

七、常見誤區辨析

Q: synchronized 修飾方法等同於 synchronized(this) { 方法體 }?
A: 語義等價,但字節碼不同:方法修飾符用 ACC_SYNCHRONIZED 標誌,JVM 直接識別;同步塊用 monitorenter/monitorexit 指令。效能無實質差異。
Q: 靜態同步方法能鎖住實例變數嗎?
A: 不能直接保護。靜態方法鎖 Class.class,實例變數屬於各實例。需在靜態方法內 synchronized(instance) 才能保護該實例變數。
Q: synchronized(new Object()) 有意義嗎?
A: 完全無意義。每次新建對象,執行緒鎖住不同對象,無互斥效果。
Q: 為什麼雙重檢查鎖 (DCL) 單例需要 volatile?
A: 防止指令重排導致其他執行緒看到「半初始化」對象。volatile 禁止重排 + 保證可見性。

八、總結圖表

┌─────────────────────────────────────────────────────────────┐
│                    synchronized 鎖對象對照表                  │
├──────────────────┬────────────────────┬──────────────────────┤
│  修飾位置         │  鎖對象             │  保護範圍             │
├──────────────────┼────────────────────┼──────────────────────┤
│  實例方法         │  this              │  該實例所有實例欄位   │
│  靜態方法         │  ClassName.class   │  所有靜態欄位         │
│  同步塊(obj)      │  obj               │  由 obj 決定          │
│  同步塊(this)     │  this              │  同實例方法           │
│  同步塊(Class.class)│ ClassName.class   │  同靜態方法           │
└──────────────────┴────────────────────┴──────────────────────┘

掌握「鎖是對象、同鎖互斥、不同鎖並行」核心邏輯,配合現代並發工具包,即能寫出高效、安全的多執行緒程式。

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