volatile 和 synchronized 的區別
在 Java 并發編程中,volatile
和 synchronized
是兩種常用的同步機制,但它們的適用場景和底層原理有顯著差異。以下是兩者的詳細對比:
1. 核心功能對比
特性 | volatile | synchronized |
---|---|---|
原子性 | 不保證復合操作的原子性(如 i++ )。 | 保證代碼塊或方法的原子性。 |
可見性 | 強制變量讀寫直接操作主內存,保證可見性。 | 通過鎖的釋放/獲取強制同步內存,保證可見性。 |
有序性 | 禁止指令重排序(內存屏障)。 | 通過鎖規則保證臨界區內的操作有序性。 |
線程阻塞 | 無阻塞,輕量級。 | 可能引發線程阻塞(鎖競爭時)。 |
適用場景 | 單寫多讀、狀態標志。 | 復合操作、臨界區資源保護。 |
2. 原子性
-
volatile:
- 不保證原子性,僅確保單次讀/寫操作的原子性。
- 示例:
多線程并發時,volatile int count = 0; count++; // 非原子操作(實際是 read-modify-write)
count++
可能導致數據競爭。
-
synchronized:
- 保證原子性,鎖內的所有操作作為一個整體執行。
- 示例:
synchronized (lock) {count++; // 原子操作 }
3. 可見性
-
volatile:
- 通過內存屏障強制線程每次讀寫
volatile
變量時直接操作主內存,跳過工作內存(CPU 緩存)。 - 示例:
volatile boolean flag = true; // 線程1修改 flag 后,線程2立即可見
- 通過內存屏障強制線程每次讀寫
-
synchronized:
- 線程進入同步塊時清空工作內存,從主內存重新加載變量;退出同步塊時將變量寫回主內存。
- 示例:
synchronized (lock) {// 操作共享變量 }
4. 有序性
-
volatile:
- 通過插入內存屏障(
StoreStore
、StoreLoad
等)禁止編譯器和處理器對volatile
變量的讀寫操作進行重排序。 - 示例:雙重檢查鎖定(DCL)中防止對象未初始化就被引用。
private static volatile Singleton instance;
- 通過插入內存屏障(
-
synchronized:
- 通過“鎖規則”實現有序性:同一時刻只有一個線程能執行同步塊,天然保證操作按代碼順序執行。
- 示例:
synchronized (lock) {// 臨界區內的操作不會重排序到鎖外 }
5. 性能開銷
-
volatile:
- 輕量級:僅通過內存屏障限制重排序,無線程阻塞和上下文切換開銷。
- 適用場景:適合低競爭或單寫多讀場景(如狀態標志)。
-
synchronized:
- 重量級:涉及鎖競爭、內核態切換(重量級鎖)和線程阻塞,開銷較大。
- 優化機制:鎖升級(偏向鎖 → 輕量級鎖 → 重量級鎖)減少無競爭時的開銷。
- 適用場景:高競爭或需要復合原子操作的場景(如計數器、資源池)。
6. 底層實現
-
volatile:
- 內存屏障:JVM 在字節碼層面插入屏障指令(如
StoreStore
、LoadLoad
)。 - 硬件支持:依賴 CPU 的緩存一致性協議(如 MESI)強制刷新內存。
- 內存屏障:JVM 在字節碼層面插入屏障指令(如
-
synchronized:
- Monitor 機制:基于對象頭的 Mark Word 實現鎖狀態管理(無鎖、偏向鎖、輕量級鎖、重量級鎖)。
- 內核態操作:重量級鎖依賴操作系統的互斥量(Mutex)實現線程阻塞。
7. 實際應用示例
(1) volatile 適用場景
- 狀態標志:
volatile boolean shutdownRequested = false; public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { /* ... */ } }
- 單例模式(DCL):
public class Singleton {private static volatile Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }
(2) synchronized 適用場景
- 線程安全的計數器:
public class Counter {private int count = 0;public synchronized void increment() { count++; }public synchronized int getCount() { return count; } }
- 資源池管理:
public class ConnectionPool {private final LinkedList<Connection> pool = new LinkedList<>();public synchronized Connection getConnection() {while (pool.isEmpty()) wait();return pool.removeFirst();}public synchronized void release(Connection conn) {pool.addLast(conn);notifyAll();} }
8. 總結對比表
維度 | volatile | synchronized |
---|---|---|
原子性 | 僅單次讀/寫原子,不保證復合操作。 | 保證代碼塊或方法的原子性。 |
可見性 | 直接操作主內存,強制刷新。 | 通過鎖同步內存。 |
有序性 | 禁止指令重排序。 | 通過鎖規則保證臨界區有序。 |
線程阻塞 | 無阻塞。 | 可能阻塞(鎖競爭時)。 |
性能開銷 | 低。 | 高(尤其是重量級鎖)。 |
適用場景 | 狀態標志、單例模式(DCL)。 | 復合操作、資源競爭、線程間協作。 |
選擇建議
-
使用
volatile
的情況:- 變量被多個線程訪問,但只有一個線程修改。
- 需要禁止指令重排序(如 DCL 單例模式)。
-
使用
synchronized
的情況:- 需要原子性操作(如
i++
、復合邏輯)。 - 多線程競爭同一資源(如連接池、計數器)。
- 需要原子性操作(如