首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 線程安全深度解析:從 synchronized 原理到 volatile、原子類與鎖優化實戰

     編輯:Java 綜合教學

1. 線程安全定義

多線程訪問某類/對象/方法時,**無需調用方額外同步**,該類/對象/方法始終表現出正確行為,則稱其為線程安全。

2. synchronized 核心機制

2.1 鎖的本質

  • 任意對象皆可作為鎖(monitorenter/monitorexit 字節碼)
  • 鎖保護的代碼區稱為「臨界區」或「互斥區」
  • 鎖類型:
      • 對象鎖:實例方法、synchronized(this)
      • 類鎖:靜態方法、synchronized(ClassName.class)

2.2 鎖重入


public class ReentrantDemo {
    public synchronized void a() { b(); }
    public synchronized void b() { c(); }
    public synchronized void c() { }
}
// 同一線程可重複獲得同一把鎖,計數器 +1/-1

2.3 異常自動釋放鎖

同步塊/方法拋出異常時,JVM 保證自動執行 monitorexit,**不會死鎖**。但業務邏輯需自行捕獲異常並決定是否回滾。

2.4 鎖對象變更導致失效


String lock = "lock";
synchronized(lock) { lock = "newLock"; } // 危險!鎖對象引用變更,其他線程可進入

鐵律:鎖對象必須 final 或不可變引用。

2.5 鎖粒度優化:同步代碼塊


// 粗粒度:整個方法同步
public synchronized void slow() { ... }

// 細粒度:僅保護共享變量操作
public void fast() {
    // 非共享耗時操作
    synchronized(sharedObj) { sharedObj.modify(); }
    // 非共享耗時操作
}

3. volatile 關鍵字

3.1 語義

  • 可見性:寫入後立即刷新到主內存,其他線程讀取時強制從主內存載入
  • 禁止指令重排序:寫屏障 + 讀屏障,保證 happens-before
  • 不保證原子性:i++ 仍非線程安全

3.2 適用場景

  • 狀態標誌位(volatile boolean running)
  • 單次寫入、多線程讀取的配置對象
  • DCL(雙重檢查鎖)單例模式

3.3 經典錯誤


volatile int count = 0;
// 多線程 count++ 依然不安全!需用 AtomicInteger

4. 原子類

4.1 核心類

類適用類型典型方法
AtomicIntegerintincrementAndGet(), addAndGet(delta)
AtomicLonglong同以上
AtomicReference<V>對象引用compareAndSet(expect, update)
AtomicIntegerArrayint[]數組元素原子更新

4.2 關鍵限制

單個方法原子 ≠ 多方法組合原子


AtomicInteger ai = new AtomicInteger(0);
// 錯誤:兩次操作中間可被插入
if (ai.get() == 5) ai.compareAndSet(5, 6); 

// 正確:CAS 循環或加鎖
synchronized(ai) { if (ai.get() == 5) ai.set(6); }

5. 髒讀案例與修復


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 合併為不可變對象原子更新

6. 字符串常量鎖陷阱


// 類加載器常量池緩存,不同類同字面量共用同一把鎖!
synchronized("LOCK") { ... }  // 極其危險,可能與第三方庫、JDK 內部死鎖
// 正確:private final Object lock = new Object();

7. 現代替代方案對比

方案優點缺點適用場景
synchronized語法簡單、JVM 優化好(偏向鎖/輕量級鎖/膨脹鎖)無法超時、無法中斷、無公平性選項簡單臨界區、鎖持有時間極短
ReentrantLock可中斷、可超時、公平/非公平、多 Condition需手動 unlock、語法繁瑣複雜同步邏輯、需嘗試獲取鎖
volatile無鎖、高性能僅可見性、無原子性狀態標誌、配置發布
原子類無鎖 CAS、高併發計數器僅單變量、ABA 問題計數器、序列號生成
LongAdder高併發下優於 AtomicLong(分段累加)最終一致性、不保證實時精確統計計數、監控指標

總結

  1. synchronized 是基石,掌握鎖對象選擇、重入、粒度、異常安全四要素。
  2. volatile 只負責可見性 + 有序性,切勿用於複合操作。
  3. 原子類 解決單變量無鎖更新,組合操作仍需加鎖。
  4. 高併發計數器首選 LongAdder / DoubleAdder。
  5. 複雜同步場景遷移至 ReentrantLock / StampedLock / CompletableFuture / 並發集合。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved