synchronized 與 volatile 是 Java 併發編程的雙基石。本文從內存模型(JMM)視角剖析兩者的語義差異、保證範圍、性能特徵,給出場景化選型建議。
// 1. 同步代碼塊:鎖對象顯式指定,粒度可控
public void output(String name) {
synchronized (this) { // 或共享 final Object lock = new Object();
for (char c : name.toCharArray()) System.out.print(c);
}
}
// 2. 同步方法:隱式鎖 this / Class對象
public synchronized void output(String name) { ... }
public static synchronized void staticMethod() { ... } // 鎖 Class對象
語義保證:互斥性 + 可見性 + 有序性。同步塊內操作對其他線程原子可見,且禁止指令重排序跨越同步塊邊界。
volatile 變量立即刷新主內存;讀取強制從主內存加載。volatile 讀寫(JMM happens-before 規則)。volatile int i; i++; 非原子(讀-改-寫三步)。class Test {
static volatile int i = 0, j = 0;
static void writer() { i++; j++; } // 兩步非原子
static void reader() { System.out.println("i=" + i + " j=" + j); }
}
// 可能輸出 j > i:writer 執行 i++ 後、j++ 前,reader 讀到舊 i、新 j
volatile 只能保證單個變量讀寫的可見/有序,無法協調多變量不變量。
| 維度 | synchronized | volatile |
|---|---|---|
| 互斥性 | ✅ 完整互斥 | ❌ 無 |
| 可見性 | ✅ 鎖釋放前刷新主內存 | ✅ 讀寫直接主內存 |
| 有序性 | ✅ 禁止重排跨越同步塊 | ✅ 禁止重排跨越讀寫 |
| 原子性 | ✅ 同步塊整體原子 | ❌ 單讀/單寫原子,復合操作非原子 |
| 阻塞性 | 可能阻塞(鎖競爭) | 不阻塞 |
| 適用對象 | 代碼塊/方法/任意對象 | 單個字段(基本類型/引用) |
| 性能特徵 | 輕量鎖/偏向鎖/重量鎖升級,競爭激烈時開銷大 | 極低,接近普通讀寫,寫入有內存屏障開銷 |
i++、check-then-act、集合非原子操作volatile boolean running = true; 線程退出信號volatile Config config;(構建完成後發布)volatile Singleton instance;AtomicLong 更好)Lock 與 Atomic* 補完ReentrantLock:可中斷、超時、公平鎖、多條件 ConditionAtomicInteger/Long/Reference:CAS 無鎖原子操作,計數器首選LongAdder:高併發計數器,分段累加,吞吐碾壓 AtomicLongvolatile 不能替代 synchronized 處理復合操作。synchronized 鎖對象必須是共享、不可變、final 引用。volatile + final 互斥(final 已保證發布安全,volatile 多餘)。Atomic*/LongAdder / ThreadLocal 避免共享。總結:synchronized 是「大錘」,保證全套語義;volatile 是「手術刀」,精準解決單變量發布可見與有序。 理解 JMM happens-before 規則,按「能用 volatile 不用 synchronized,能用 Atomic 不用鎖,能用無共享不共享」原則選型,是併發編程的成熟標誌。