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

Java synchronized 關鍵字深度解析:四種鎖模式與實戰原理圖解

     編輯:Java 綜合教學

Java synchronized 關鍵字深度解析:四種鎖模式與實戰原理圖解

synchronized 是 Java 並發編程的基石,通過對象監視器實現線程互斥與內存可見性。本文系統講解四種使用模式、鎖對象確定規則、執行流程演示與最佳實踐,助你徹底掌握「鎖住的是誰」。

核心原則:一個對象一把鎖

Java 中每個對象實例天生自帶一把監視器鎖。synchronized 的本質是獲取指定對象的鎖,執行同步代碼,退出時釋放鎖。靜態方法鎖住的是 Class 對象。

四大使用模式對比表

模式代碼形式鎖對象作用範圍典型場景
實例方法 public synchronized void m() this (調用對象) 同一實例的所有同步實例方法互斥 實例狀態修改
靜態方法 public static synchronized void m() ClassName.class 全類加載器範圍內所有調用互斥 靜態變量修改、單例初始化
實例方法同步塊 synchronized(this) { ... } this 或自定義對象 細粒度鎖,縮小同步範圍 部分代碼需同步、避免死鎖
靜態方法同步塊 synchronized(ClassName.class) { ... } ClassName.class 或自定義靜態對象 類級別細粒度鎖 靜態資源訪問控制

模式一:實例方法同步 — 鎖住 this

class Counter {
    private int count = 0;
    public synchronized void increment() {  // 鎖 this
        count++;
    }
    public synchronized int getCount() {    // 鎖 this
        return count;
    }
}

// 測試:兩線程同時調用同一對象的 increment(),必須串行執行
Counter c = new Counter();
new Thread(() -> { for(int i=0;i<10000;i++) c.increment(); }, "t1").start();
new Thread(() -> { for(int i=0;i<10000;i++) c.increment(); }, "t2").start();
// 最終 c.getCount() == 20000

關鍵點:若兩線程調用不同實例的同步方法,不互斥(鎖住不同對象)。

模式二:靜態方法同步 — 鎖住 Class 對象

class StaticCounter {
    private static int count = 0;
    public static synchronized void increment() { // 鎖 StaticCounter.class
        count++;
    }
}

// 即使創建多個實例,靜態方法同步仍全局互斥
// 因為所有實例共享同一個 Class 對象

模式三:同步塊 — 精確控制鎖對象與範圍

class FineGrained {
    private final Object lock1 = new Object(); // 專用鎖對象
    private final Object lock2 = new Object();
    private int a = 0, b = 0;

    public void incA() {
        synchronized(lock1) { a++; } // 僅鎖住 a 相關操作
    }
    public void incB() {
        synchronized(lock2) { b++; } // 僅鎖住 b 相關操作,與 incA 並行無衝突
    }
}

優勢:

  • 縮小同步範圍,提高並發度。
  • 可使用私有 final 對象作為鎖,防止外部代碼獲取同一把鎖導致死鎖或活鎖。
  • 避免鎖住 this 或 Class 造成過度競爭。

模式四:靜態同步塊 — 類級別精細控制

class StaticFineGrained {
    private static final Object LOCK = new Object();
    private static Map cache = new HashMap<>();

    public static String get(String key) {
        synchronized(LOCK) { return cache.get(key); }
    }
    public static void put(String key, String value) {
        synchronized(LOCK) { cache.put(key, value); }
    }
}

執行流程演示:線程阻塞與喚醒

class Demo {
    public synchronized void slow() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " 獲得鎖,開始睡眠");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + " 釋放鎖");
    }
}

Demo d = new Demo();
new Thread(() -> { try { d.slow(); } catch (Exception e) {} }, "T1").start();
Thread.sleep(100); // 確保 T1 先獲取鎖
new Thread(() -> { try { d.slow(); } catch (Exception e) {} }, "T2").start();

/*
輸出順序:
T1 獲得鎖,開始睡眠
... 2秒後 ...
T1 釋放鎖
T2 獲得鎖,開始睡眠
... 2秒後 ...
T2 釋放鎖
*/

進階:synchronized 底層優化 (JDK 1.6+)

偏向鎖

無競爭時,線程獲取鎖僅需 CAS 設置 ThreadID,極低開銷。

輕量級鎖

輕度競爭,線程自旋 CAS 獲取鎖,不進內核。

重量級鎖

激烈競爭,線程阻塞進入內核等待,OS 調度。

鎖消除/粗化

JIT 編譯器優化:逃逸分析發現鎖無競爭則消除;連續同步塊合併。

最佳實踐檢查清單

  1. ✅ 優先使用私有 final 對象作為鎖:private final Object lock = new Object();
  2. ✅ 同步塊代碼越少越好:將非共享計算移出同步塊。
  3. ✅ 避免在同步塊中調用異步/耗時操作:IO、睡眠、遠程調用。
  4. ✅ 警惕死鎖:固定獲取鎖順序,或使用 ReentrantLock.tryLock(timeout)。
  5. ✅ 讀多寫少場景考慮 ReadWriteLock / StampedLock:性能更優。
  6. ✅ JDK 8+ 推薦 LongAdder / ConcurrentHashMap 替代 synchronized 計數/映射:無鎖/分段鎖吞吐更高。

常見誤區辨析

誤區真相
synchronized(this) 鎖住方法鎖住的是 this 對象,影響所有同步代碼塊/方法
靜態方法鎖住 this靜態方法無 this,鎖住 Class 對象
String 常量做鎖安全極危險!常量池共享,不同類加載器可能共用同一把鎖
synchronized 可中斷不可中斷,只能等待;需中斷用 Lock.lockInterruptibly()

synchronized 雖然是重量級關鍵字,但經過多版本優化,在中低並發場景下表現優異,語義清晰且由 JVM 保證釋放。深入理解「鎖住的是哪個對象」,是寫出正確高效並發代碼的前提。

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