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

Java 併發底層:原子操作實現原理與 CAS 深度解析

     編輯:Java 相關知識

前言

原子操作是併發編程的基石。本文從 CPU 硬體指令(總線鎖/緩存鎖)、JVM 內部實現(Unsafe + CAS)、Java 並發包應用(AtomicInteger、AtomicReference),到 CAS 三大經典問題(ABA、自旋開銷、單變量限制)及鎖機制關係,全鏈路剖析原子操作實現原理。

一、術語定義

原子操作相關術語圖

二、CPU 如何保證原子性

1. 基礎內存操作自動原子

  • 讀/寫單字節、對齊的 16/32/64 位數據(單緩存行內)天然原子。
  • 跨緩存行、跨總線寬度、跨頁表的訪問不保證原子。

2. 總線鎖

  • CPU 發出 LOCK# 信號,佔用總線,阻塞其他 CPU 內存訪問。
  • 開銷大:鎖定期間所有核心無法訪問內存。

3. 緩存鎖(現代 CPU 主流)

  • 鎖定緩存行(通常 64 字節),不發 LOCK# 信號。
  • 利用緩存一致性協議(MESI):修改時將該行標為「修改態」,其他核心該行失效,強制從主存讀取。
  • 不適用場景:數據未緩存、跨緩存行、舊 CPU(486/奔騰)不支援。

4. LOCK 前綴指令

CPU 提供帶 LOCK 前綴的指令自動使用上述機制:

  • 位操作:BTS、BTR、BTC
  • 交換/比較交換:XADD、CMPXCHG
  • 算術/邏輯:ADD、OR 等

三、Java 如何實現原子操作

1. 循環 CAS(自旋 CAS)

核心思路:while(true) { 讀舊值; 計算新值; if (CAS(舊值, 新值)) break; }

private void safeCount() {
    for (;;) {
        int i = atomicI.get();
        boolean suc = atomicI.compareAndSet(i, ++i);
        if (suc) break;
    }
}

AtomicInteger.compareAndSet 最終調用 Unsafe.compareAndSwapInt → JVM 內部 CMPXCHG 指令。

2. CAS 三大問題及解決方案

問題描述解決方案
ABA 問題值 A→B→A,CAS 以為未變版本號戳:AtomicStampedReference,compareAndSet(ref, newRef, stamp, newStamp)
自旋開銷大長時間 CAS 失敗佔用 CPUJVM 支援 pause 指令(延遲流水線、避免內存順序衝突刷新管線)
只能保證單變量多變量邀請無法原子1. 用鎖 2. 合併為一個對象用 AtomicReference 3. JDK 1.5+ AtomicReference 封裝多字段

3. AtomicStampedReference 解決 ABA

public boolean compareAndSet(
    V expectedReference, V newReference,
    int expectedStamp, int newStamp) { ... }

每次更新同時遞增 stamp,A-B-A 變為 1A-2B-3A,CAS 檢測到 stamp 變化而失敗。

4. 鎖機制:底層同樣依賴 CAS

  • synchronized(偏向鎖除外)進入/退出同步塊時,均使用循環 CAS 獲取/釋放鎖標記。
  • JDK 1.6+ 鎖優化:偏向鎖 → 輕量級鎖(CAS) → 重量級鎖(OS 互斥量)。
  • 參考《Java SE 1.6 中的 Synchronized》深度解析。

四、實戰對比:CAS vs 非線程安全計數器

public class Counter {
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Counter cas = new Counter();
        List ts = new ArrayList<>(600);
        long start = System.currentTimeMillis();
        for (int j = 0; j < 100; j++) {
            Thread t = new Thread(() -> {
                for (int k = 0; k < 10000; k++) {
                    cas.count();      // 非線程安全
                    cas.safeCount();  // CAS 線程安全
                }
            });
            ts.add(t);
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> { try { t.join(); } catch (InterruptedException e) {} });
        System.out.println("非安全計數: " + cas.i);          // < 1,000,000
        System.out.println("CAS 安全計數: " + cas.atomicI.get()); // = 1,000,000
        System.out.println("耗時: " + (System.currentTimeMillis() - start) + "ms");
    }

    private void safeCount() { /* 上述 CAS 循環 */ }
    private void count() { i++; }
}

五、總結

  1. 硬體基石:現代 CPU 以緩存鎖(MESI 協議)實現細粒度原子操作,避免總線鎖性能懸崖。
  2. JVM 橋接:Unsafe.CAS 直接映射 CMPXCHG 指令,Java 並發包全部構建於此。
  3. CAS 模式:讀-算-CAS 循環,無鎖、高併發、但需處理 ABA、自旋、單變量限制。
  4. 工程選型:
    • 單數值計數/標記 → AtomicLong/Integer/Boolean
    • 單對象引用更新 → AtomicReference
    • 需版本號防 ABA → AtomicStampedReference / AtomicMarkableReference
    • 多變量強一致性 → synchronized / ReentrantLock / STM(如 Multiverse)
  5. 鎖與 CAS 共生:鎖內部用 CAS 實現;CAS 失敗重試過多時可退化為鎖(如 LongAdder 熱點分離)。

理解原子操作從 CPU 指令到 Java API 的完整鏈路,是寫出高性能、正確併發代碼的前提。下次使用 AtomicInteger 時,腦海中應浮現 CMPXCHG、緩存行、MESI、自旋循環這條完整鏈路。

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