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

Java 無鎖編程深度解析:CAS 原理、Atomic 類族與 ABA 問題解決

     編輯:Java 相關知識

為什麼需要無鎖?

傳統 synchronized/ReentrantLock 屬於悲觀鎖:假設衝突必發生,阻塞線程等待。高併發下上下文切換、內核態切換開銷大。無鎖採用樂觀鎖策略:假設衝突少見,嘗試直接更新,失敗則重試(自旋),避免線程掛起,吞吐量顯著提升。

核心原理:CAS (Compare-And-Swap)

硬件指令級原子性

CAS 由 CPU 指令 cmpxchg (x86) / LDXR/STXR (ARM) 保證原子性,不可分割。JVM 通過 Unsafe.compareAndSwapInt/Long/Object 內部方法暴露給 Java 層。

CAS 三元組語義

// 伪代码
boolean compareAndSet(V expected, V newValue) {
    if (currentValue == expected) {
        currentValue = newValue;
        return true;
    }
    return false;
}

只有當內存值 等於預期值 時才更新為新值,返回成功;否則不修改,返回失敗。調用者通常在循環中重試 (自旋)。

Java 原子類族全景

類別代表類適用場景
基本類型AtomicInteger, AtomicLong, AtomicBoolean計數器、序列號、開關標誌
引用類型AtomicReference<V>對象引用原子更新、無鎖堆棧/隊列節點
數組類型AtomicIntegerArray, AtomicReferenceArray數組元素獨立原子更新
字段更新器AtomicIntegerFieldUpdater, AtomicReferenceFieldUpdater對現有類的 volatile 字段賦予原子性,不改動原類結構
帶版本號引用AtomicStampedReference, AtomicMarkableReference解決 ABA 問題
累加器LongAdder, DoubleAdder, LongAccumulator高併發計數器,分段累加降低 CAS 衝突 (JDK 8+)

深度剖析:AtomicInteger 典型實現

public final int getAndIncrement() {
    for (;;) { // 自旋循環
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next)) // CAS 嘗試更新
            return current;
    }
}

valueOffset 通過 Unsafe.objectFieldOffset 獲取字段在對象內存中的偏移量,CAS 直接操作該偏移地址。

ABA 問題與解決方案

問題場景

線程 1 讀取值 A,準備 CAS 更新為 B。線程 2 將 A→B→A。線程 1 CAS 時發現值仍為 A,誤以為未被修改,更新成功,但實際狀態已發生變化。對於單純計數器無影響,但對於節點引用、狀態機等場景會導致邏輯錯誤。

AtomicStampedReference 解決

內部維護 Pair<V> { reference, stamp },每次更新遞增 stamp(時間戳/版本號)。CAS 同時比較引用與 stamp。

AtomicStampedReference money = new AtomicStampedReference<>(19, 0);

// 充值線程:余額 < 20 才充值,且每次操作 stamp+1
int stamp = money.getStamp();
if (money.compareAndSet(19, 39, stamp, stamp + 1)) { ... }

// 消費線程:同樣檢查 stamp

即使值 19→39→19,stamp 已從 0→1→2,第二次 CAS 會因 stamp 不匹配而失敗。

AtomicMarkableReference 輕量級

僅需一個 boolean 標記(如「已邏輯刪除」),不需要完整版本號。

Unsafe 類:原子操作的底層入口

  • compareAndSwapInt/Long/Object:CAS 操作。
  • getAndAddInt/Long:原子增減。
  • putOrderedInt/Long:有序寫入(釋放語義,弱於 volatile)。
  • park/unpark:線程阻塞/喚醒,LockSupport 底層依賴。
  • 警告:sun.misc.Unsafe 非標準 API,JDK 9+ 模塊化限制訪問,建議優先使用 VarHandle (JDK 9+) 或原子類封裝。

VarHandle:現代替代方案 (JDK 9+)

// 靜態字段 VarHandle
private static final VarHandle COUNTER;
static {
    try {
        COUNTER = MethodHandles.lookup()
            .findVarHandle(Counter.class, "count", int.class);
    } catch (Exception e) { throw new ExceptionInInitializerError(e); }
}

// 使用
COUNTER.getAndAdd(this, 1); // 原子增
COUNTER.compareAndSet(this, 0, 1); // CAS

VarHandle 提供標準化、模塊化友好的底層內存操作 API,支援多種內存排序模式(plain, opaque, acquire/release, volatile)。

性能對比與選型建議

場景推薦方案理由
單一計數器,中低併發AtomicLong簡單、語義清晰
高併發計數器(統計、限流)LongAdder分段累加,CAS 衝突極低,吞吐量碾壓 AtomicLong
對象引用原子更新AtomicReference泛型支援、無 ABA 風險場景
無鎖數據結構節點(鏈表、隊列)AtomicStampedReference解決 ABA,保證線性一致性
現有類字段賦予原子性Atomic*FieldUpdater無侵入、字段需 volatile

總結

無鎖編程基於硬件 CAS 指令,通過樂觀重試實現高併發下的優異性能。Java 提供豐富的原子類族覆蓋各類場景,核心在於理解 CAS 語義、ABA 問題、自旋開銷、內存可見性。實際開發中,優先使用 LongAdder 等高級累加器、ConcurrentHashMap 等並發容器,僅在構建自定義無鎖數據結構時直接操作 AtomicReference/VarHandle。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved