多線程訪問某類/對象/方法時,**無需調用方額外同步**,該類/對象/方法始終表現出正確行為,則稱其為線程安全。
monitorenter/monitorexit 字節碼)synchronized(this)synchronized(ClassName.class)
public class ReentrantDemo {
public synchronized void a() { b(); }
public synchronized void b() { c(); }
public synchronized void c() { }
}
// 同一線程可重複獲得同一把鎖,計數器 +1/-1
同步塊/方法拋出異常時,JVM 保證自動執行 monitorexit,**不會死鎖**。但業務邏輯需自行捕獲異常並決定是否回滾。
String lock = "lock";
synchronized(lock) { lock = "newLock"; } // 危險!鎖對象引用變更,其他線程可進入
鐵律:鎖對象必須 final 或不可變引用。
// 粗粒度:整個方法同步
public synchronized void slow() { ... }
// 細粒度:僅保護共享變量操作
public void fast() {
// 非共享耗時操作
synchronized(sharedObj) { sharedObj.modify(); }
// 非共享耗時操作
}
i++ 仍非線程安全volatile boolean running)
volatile int count = 0;
// 多線程 count++ 依然不安全!需用 AtomicInteger
| 類 | 適用類型 | 典型方法 |
|---|---|---|
AtomicInteger | int | incrementAndGet(), addAndGet(delta) |
AtomicLong | long | 同以上 |
AtomicReference<V> | 對象引用 | compareAndSet(expect, update) |
AtomicIntegerArray | int[] | 數組元素原子更新 |
單個方法原子 ≠ 多方法組合原子
AtomicInteger ai = new AtomicInteger(0);
// 錯誤:兩次操作中間可被插入
if (ai.get() == 5) ai.compareAndSet(5, 6);
// 正確:CAS 循環或加鎖
synchronized(ai) { if (ai.get() == 5) ai.set(6); }
class Data {
private String user = "init";
private String pwd = "init";
public synchronized void write(String u, String p) {
this.user = u;
sleep(2000); // 模擬耗時
this.pwd = p;
}
public void read() { // 缺少 synchronized!
System.out.println(user + " / " + pwd);
}
}
// 主線程 write("A","1"),子線程 1s 後 read() → 可能讀到 "A / init"(髒讀)
// 修復:read() 也加 synchronized,或將 user/pwd 合併為不可變對象原子更新
// 類加載器常量池緩存,不同類同字面量共用同一把鎖!
synchronized("LOCK") { ... } // 極其危險,可能與第三方庫、JDK 內部死鎖
// 正確:private final Object lock = new Object();
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
synchronized | 語法簡單、JVM 優化好(偏向鎖/輕量級鎖/膨脹鎖) | 無法超時、無法中斷、無公平性選項 | 簡單臨界區、鎖持有時間極短 |
ReentrantLock | 可中斷、可超時、公平/非公平、多 Condition | 需手動 unlock、語法繁瑣 | 複雜同步邏輯、需嘗試獲取鎖 |
volatile | 無鎖、高性能 | 僅可見性、無原子性 | 狀態標誌、配置發布 |
| 原子類 | 無鎖 CAS、高併發計數器 | 僅單變量、ABA 問題 | 計數器、序列號生成 |
LongAdder | 高併發下優於 AtomicLong(分段累加) | 最終一致性、不保證實時精確 | 統計計數、監控指標 |