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

Java 併發編程:CAS 機制原理、效能陷阱與 synchronized 優化對比

     編輯:Java 相關知識

Compare-And-Swap (CAS) 是 Java 併發包(java.util.concurrent.atomic)實現無鎖線程安全的核心技術。但「無鎖 ≠ 高效」,在高併發爭用下 CAS 自旋可能嚴重拖垮效能。本文通過原理剖析、基準測試對比、JDK 鎖優化演進,揭示 CAS 適用邊界與最佳選擇策略。

一、CAS 原理與 AtomicInteger 實現

// AtomicInteger.getAndIncrement() 核心邏輯
public final int getAndIncrement() {
    for (;;) {  // 自旋循環
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))  // CAS 嘗試更新
            return current;
        // 失敗則重試
    }
}
  • compareAndSet 底層調用 Unsafe.compareAndSwapInt → CPU LOCK CMPXCHG 指令
  • 原子性由硬件保證,無需進入內核態、無線程切換開銷

二、適用場景對比:CAS vs synchronized

三、基準測試:1000 線程累加 2000 萬次

public class CASBenchmark {
    static final int THREADS = 1000;
    static final int TARGET = 20_000_000;
    
    AtomicInteger casCounter = new AtomicInteger(0);
    int syncCounter = 0;
    Object syncLock = new Object();
    
    void casTest() throws InterruptedException {
        Thread[] ts = new Thread[THREADS];
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREADS; i++) {
            ts[i] = new Thread(() -> {
                while (casCounter.get() < TARGET) casCounter.getAndIncrement();
            });
            ts[i].start();
        }
        for (Thread t : ts) t.join();
        System.out.println("CAS 耗時: " + (System.currentTimeMillis() - start) + "ms");
    }
    
    void syncTest() throws InterruptedException {
        Thread[] ts = new Thread[THREADS];
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREADS; i++) {
            ts[i] = new Thread(() -> {
                while (true) {
                    synchronized (syncLock) {
                        if (syncCounter >= TARGET) break;
                        syncCounter++;
                    }
                }
            });
            ts[i].start();
        }
        for (Thread t : ts) t.join();
        System.out.println("synchronized 耗時: " + (System.currentTimeMillis() - start) + "ms");
    }
}

測試結果(雙核 CPU,JDK 1.7)

  • 純計數:CAS ~4500ms,synchronized ~1200ms → CAS 完敗(自旋過度消耗 CPU)
  • 加入 40ms 文件讀取模擬業務:CAS ~42000ms,synchronized ~42000ms → 持平(業務耗時掩蓋同步開銷)

四、為什麼 synchronized 反超 CAS?

JDK 1.6+ 引入重磅優化:

  • 偏向鎖:無競爭時無 CAS 開銷,直接標記線程 ID
  • 輕量級鎖:輕度競爭用 CAS 自旋,避免進入內核
  • 自適應自旋:根據歷史自旋成功率動態調整自旋次數
  • 鎖消除/鎖粗化:JIT 編譯器優化冗餘同步
  • 阻塞隊列:重度競爭直接掛起線程,不佔用 CPU

綜合效果:synchronized 在中高併發下綜合效能優於手寫 CAS 循環。

五、CAS 典型陷阱與避坑指南

  1. ABA 問題:值 A→B→A,CAS 認為未變。解決:AtomicStampedReference 帶版本號
  2. 自旋過度:高併發長循環燒 CPU。解決:改用 synchronized 或 LongAdder(分段計數)
  3. 只能單變量:多變量不變性需用鎖或 STM
  4. 內存屏障語義:CAS 具有 volatile 讀寫語義,但非萬能

六、LongAdder:高併發計數器的正解

LongAdder adder = new LongAdder();
adder.increment();  // 內部多段槽位分散熱點,最終 sum() 聚合
// 效能遠超 AtomicLong,適合統計計數、監控指標

七、總結

CAS 是併發編程的基石,但不是銀彈。低併發輕操作用 CAS(Atomic*),中高併發或複雜邏輯用 synchronized/Lock,超高併發計數用 LongAdder。理解 JDK 鎖優化演進,信任 synchronized 的現代表現,才是成熟併發工程師的標誌。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景推薦理由
低併發、輕量操作CAS無上下文切換,自旋幾率低,延遲極低
高併發、長耗時操作synchronizedCAS 自旋消耗 CPU,synchronized 進入阻塞隊列更高效
計數器、狀態標記CAS (AtomicLong/AtomicReference)單變量原子操作,語義清晰
複雜臨界區、多變量協同synchronized / LockCAS 只能保證單變量原子性