一個類有一個全局變量 m,多線程對它進行增加操作,如何保證線程安全?
在多線程環境下對共享變量進行修改時,確保線程安全的關鍵是保證操作的原子性、可見性和有序性。以下是針對全局變量 m
的多線程自增操作的線程安全解決方案:
1. 使用 synchronized
關鍵字(悲觀鎖)
public class Counter {private static int m = 0;// 實例方法同步(鎖住當前實例)public synchronized void increment() {m++;}// 靜態方法同步(鎖住類的 Class 對象)public static synchronized void staticIncrement() {m++;}
}
特點:
- 簡單直接,但并發性能較低(鎖粒度較大)。
- 適用于低競爭場景。
2. 使用 ReentrantLock
(顯式鎖)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Counter {private static int m = 0;private static final Lock lock = new ReentrantLock();public static void increment() {lock.lock();try {m++;} finally {lock.unlock();}}
}
特點:
- 更靈活(支持嘗試鎖、可中斷鎖等),但需手動管理鎖的釋放。
- 適用于需要細粒度控制的場景。
3. 使用原子類 AtomicInteger
(無鎖,CAS 實現)推薦!
import java.util.concurrent.atomic.AtomicInteger;public class Counter {private static final AtomicInteger m = new AtomicInteger(0);public static void increment() {m.incrementAndGet(); // 原子性自增}
}
特點:
- 基于 CAS(Compare and Swap)實現無鎖并發,性能高。
- 適用于高并發場景(如計數器、累加器)。
5. 使用 LongAdder
(高并發優化)
import java.util.concurrent.atomic.LongAdder;public class Counter {private static final LongAdder m = new LongAdder();public static void increment() {m.increment(); // 分段累加,減少競爭}public static long get() {return m.sum();}
}
特點:
- 在極高并發場景下性能優于
AtomicInteger
,但讀取最終結果時需調用sum()
。 - 適用于寫多讀少的場景(如統計點擊數)。
關鍵對比
方法 | 原理 | 性能 | 適用場景 |
| 悲觀鎖 | 低-中 | 低競爭、簡單同步 |
| 顯式鎖 | 中 | 需要靈活控制鎖 |
| CAS 無鎖 | 高 | 高并發、簡單原子操作 |
| 分段 CAS | 極高 | 超高并發寫、最終一致性讀 |
為什么 volatile
不能單獨解決自增線程安全?
volatile
僅保證:
- 可見性:線程修改后其他線程立即可見新值。
- 有序性:禁止指令重排序優化。
但 m++
是非原子操作(包含讀、改、寫三步),多個線程可能同時讀到相同的舊值并覆蓋寫入,導致結果錯誤。例如:
Thread1: 讀取 m=5 → 計算 5+1=6 → 準備寫入 6
Thread2: 讀取 m=5 → 計算 5+1=6 → 寫入 6
最終結果 m=6(實際應為 7)
最佳實踐
- 優先選擇原子類(如
AtomicInteger
或LongAdder
),兼顧性能和易用性。 - 若需復雜同步邏輯(如條件等待),使用
ReentrantLock
。 - 避免過度依賴
synchronized
,尤其是在高并發場景下。
通過合理選擇同步機制,可以確保線程安全的同時最大化性能。