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 或自定義靜態對象 |
類級別細粒度鎖 | 靜態資源訪問控制 |
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 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 並行無衝突
}
}
優勢:
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 釋放鎖
*/
無競爭時,線程獲取鎖僅需 CAS 設置 ThreadID,極低開銷。
輕度競爭,線程自旋 CAS 獲取鎖,不進內核。
激烈競爭,線程阻塞進入內核等待,OS 調度。
JIT 編譯器優化:逃逸分析發現鎖無競爭則消除;連續同步塊合併。
private final Object lock = new Object();ReentrantLock.tryLock(timeout)。| 誤區 | 真相 |
|---|---|
| synchronized(this) 鎖住方法 | 鎖住的是 this 對象,影響所有同步代碼塊/方法 |
| 靜態方法鎖住 this | 靜態方法無 this,鎖住 Class 對象 |
| String 常量做鎖安全 | 極危險!常量池共享,不同類加載器可能共用同一把鎖 |
| synchronized 可中斷 | 不可中斷,只能等待;需中斷用 Lock.lockInterruptibly() |
synchronized 雖然是重量級關鍵字,但經過多版本優化,在中低並發場景下表現優異,語義清晰且由 JVM 保證釋放。深入理解「鎖住的是哪個對象」,是寫出正確高效並發代碼的前提。