09-Java原子类与CAS机制

107 阅读3分钟

Java原子类与CAS机制:从入门到ABA问题解决方案

一、原子操作底层原理

1. 硬件级支持:CPU原子指令

指令作用对应Java实现
LOCK CMPXCHG比较并交换(32/64位)Unsafe.compareAndSwapInt
LOCK XADD原子加法AtomicInteger.addAndGet
MFENCE内存屏障保证可见性volatile变量读写

2. Java原子类体系结构

graph BT
    A[AtomicInteger] --> B[Number]
    C[AtomicReference] --> D[Object]
    E[AtomicStampedReference] --> C
    F[LongAdder] --> G[Striped64]

二、核心原子类源码剖析

1. AtomicInteger实现原理

public class AtomicInteger {
    private volatile int value;
    
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }
    
    // Unsafe底层调用
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset); // 原子读
        } while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS自旋
        return v;
    }
}

2. LongAdder高性能秘密

  • Cell数组分散竞争:通过@Contended避免伪共享
  • 最终一致性sum()时合并所有Cell值
// Striped64部分源码
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        // 竞争激烈时使用Cell数组
        if ((c = getProbe()) == 0) {
            ThreadLocalRandom.current(); // 初始化探针
            wasUncontended = true;
        }
        if (cs == null || (m = cs.length - 1) < 0)
            collide = false;
        else if ((c = cs[m & c]) == null)
            // 创建新Cell...
    }
}

三、ABA问题与解决方案

1. 问题复现场景

AtomicReference<String> ref = new AtomicReference<>("A");
// 线程1:A→B→A
ref.compareAndSet("A", "B"); 
ref.compareAndSet("B", "A");

// 线程2:检查到值仍是A,但中间状态已变化
ref.compareAndSet("A", "C"); // 成功,但可能不符合业务预期

2. 解决方案对比

方案实现原理优缺点
版本号(AtomicStampedReference)附加int版本号标记精确但内存开销较大
时间戳(AtomicMarkableReference)使用boolean标记内存友好但精度较低
业务唯一ID使用自增ID代替原始值需要业务改造
// AtomicStampedReference示例
AtomicStampedReference<String> ref = 
    new AtomicStampedReference<>("A", 0);

int[] stamp = new int[1];
String current = ref.get(stamp); // 同时获取值和版本号
ref.compareAndSet(current, "C", stamp[0], stamp[0] + 1);

四、JDK8+增强型原子类

1. Accumulator类使用场景

LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
// 并行累加
IntStream.range(0, 100).parallel().forEach(i -> accumulator.accumulate(i));
System.out.println(accumulator.get()); // 输出4950

2. 性能对比测试

操作AtomicIntegerLongAdderLongAccumulator
100万次单线程累加12ms15ms18ms
100万次100并发累加420ms32ms35ms

五、实战:实现无锁栈

1. Treiber Stack实现

public class ConcurrentStack<E> {
    private AtomicReference<Node<E>> top = new AtomicReference<>();
    
    public void push(E item) {
        Node<E> newHead = new Node<>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }
    
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = top.get();
            if (oldHead == null) return null;
            newHead = oldHead.next;
        } while (!top.compareAndSet(oldHead, newHead));
        return oldHead.item;
    }
    
    private static class Node<E> {
        final E item;
        Node<E> next;
        Node(E item) { this.item = item; }
    }
}

六、常见问题QA

💬 Q1:CAS自旋导致CPU飙升怎么办?

解决方案

  1. 使用LongAdder替代AtomicLong
  2. 添加短暂Yield:Thread.yield()
  3. 退化为锁竞争(如synchronized

💬 Q2:AtomicInteger和volatile的区别?

核心差异

维度AtomicIntegervolatile
原子性支持(CAS)不支持(仅可见性)
复合操作提供addAndGet等方法需手动同步
性能高并发下更好简单场景更高效

💬 Q3:什么时候该用字段更新器?

适用场景

  • 需要原子更新已有类的字段(如第三方库)
  • 内存敏感场景(比AtomicReference省内存)
// 示例:原子更新Person的age字段
AtomicIntegerFieldUpdater<Person> updater = 
    AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
Person p = new Person();
updater.incrementAndGet(p); // 原子增加age

性能优化建议

  1. 高并发计数优先使用LongAdder
  2. 检查sun.misc.Unsafe的使用(JDK9+推荐VarHandle
  3. 避免在热点代码中使用AtomicStampedReference(内存开销大)

通过-XX:+PrintAssembly可查看CAS对应的汇编指令