原子操作

原子操作:Java 并发安全的基石

在多线程环境中,对共享变量的操作若不加以控制,可能导致数据不一致(如i++这类看似原子的操作,实际包含 “读取 - 修改 - 写入” 三步)。Java 的java.util.concurrent.atomic包提供了一系列原子操作类,通过底层的 CAS(Compare-And-Swap)机制,确保操作的原子性,避免线程安全问题。

原子操作类的核心原理

原子操作类的本质是通过Unsafe 类实现的 CAS 操作,其核心思想是:乐观锁—— 假设操作不会冲突,若冲突则重试,直到成功。以最常用的AtomicInteger为例,其内部通过volatile修饰的value存储值,并借助 Unsafe 的 native 方法实现原子更新。

AtomicInteger 的核心源码解析

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 用于执行底层CAS操作的Unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // value字段在对象中的内存偏移量(用于CAS定位)
    private static final long valueOffset;

    static {
        try {
            // 获取value字段的内存偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { 
            throw new Error(ex); 
        }
    }

    // 存储实际值,用volatile保证可见性
    private volatile int value;

    // 核心方法解析:

    // 1. 获取当前值(volatile保证可见性)
    public final int get() {
        return value;
    }

    // 2. 原子更新:若当前值等于预期值,则更新为新值
    public final boolean compareAndSet(int expect, int update) {
        // 调用Unsafe的CAS方法,原子性地比较并替换
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    // 3. 原子自增(i++的线程安全版)
    public final int getAndIncrement() {
        // 先获取当前值,再加1(底层通过循环CAS实现)
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    // 4. 原子自增(++i的线程安全版)
    public final int incrementAndGet() {
        // 先加1,再返回新值
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // 其他方法:getAndDecrement()、decrementAndGet()、getAndAdd()等原理类似
}

CAS 操作的底层逻辑

unsafe.compareAndSwapInt(this, valueOffset, expect, update)的工作流程:

  1. 从内存偏移量valueOffset处读取当前值current
  2. 比较current与expect:
    • 若相等,将update写入该内存地址,返回true
    • 若不等,不做操作,返回false

整个过程由 CPU 指令原子性执行,无需加锁,效率远高于synchronized

原子操作类的常见类型与用法

java.util.concurrent.atomic包提供了多种原子类,覆盖基本类型、引用类型、数组等场景:

类型 代表类 用途
基本类型 AtomicInteger 原子更新 int 值
AtomicLong 原子更新 long 值
AtomicBoolean 原子更新 boolean 值
引用类型 AtomicReference 原子更新对象引用
AtomicStampedReference 带版本号的原子引用(解决 ABA 问题)
数组类型 AtomicIntegerArray 原子更新 int 数组元素
字段更新器 AtomicIntegerFieldUpdater 原子更新对象的 int 字段(无需字段为 volatile)

示例:使用 AtomicInteger 实现线程安全计数器

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    // 线程安全的自增
    public void increment() {
        count.incrementAndGet();
    }

    // 获取当前计数
    public int getCount() {
        return count.get();
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicCounter counter = new AtomicCounter();
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 10个线程各自增1000次
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.increment();
                }
            });
        }

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);

        System.out.println("最终计数:" + counter.getCount()); // 输出10000(无并发问题)
    }
}

原子操作的局限性与解决思路

1. ABA 问题

问题:若变量值从 A 变为 B,再变回 A,CAS 会误认为值未修改,导致更新错误。
解决:使用AtomicStampedReference,为值绑定一个版本号,更新时同时检查值和版本号。

AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0);
int stamp = stampedRef.getStamp(); // 获取当前版本号

// 只有值为100且版本号为stamp时,才更新为200并递增版本号
boolean success = stampedRef.compareAndSet(100, 200, stamp, stamp + 1);

2. 高并发下的自旋开销

CAS 失败时会重试(自旋),高并发场景下可能导致大量线程空转,消耗 CPU 资源。
解决

  • 结合LongAdder(JDK 8+):高并发时分散热点,通过多个单元格累加结果,降低冲突;
  • 合理控制并发粒度,避免过度竞争。

指令重排序对原子操作的影响

JVM 和 CPU 为优化性能,会对指令进行重排序(在不影响单线程语义的前提下调整执行顺序)。但原子操作依赖操作的有序性,若重排序破坏了执行顺序,可能导致逻辑错误。

重排序的类型

  1. 编译器重排序:编译器调整代码执行顺序;
  2. 指令级重排序:CPU 将多条指令并行执行,若无数据依赖则调整顺序;
  3. 内存重排序:由于缓存和读写缓冲区,加载 / 存储操作可能看似乱序。

原子操作如何避免重排序?

  • volatile 的内存屏障:原子类中的value字段用volatile修饰,其读写会插入内存屏障(如StoreLoad),禁止重排序;
  • CAS 的原子性:CAS 操作由 CPU 指令保证原子性,不会被重排序打断。