cas 存在的典型ABA 问题举例

更合适的例子:无锁栈(Treiber Stack)的 ABA 问题

ABA 问题在 无锁数据结构 (如栈、队列)中更常见,而余额修改场景确实不太典型。我们换一个更合适的例子:无锁栈的 ABA 问题


1. 问题场景:无锁栈(Treiber Stack)

实现一个线程安全的栈 ,使用 AtomicReference 管理栈顶:

java 复制代码
import java.util.concurrent.atomic.AtomicReference;

class ConcurrentStack<T> {
    private static class Node<T> {
        final T value;
        Node<T> next;

        Node(T value) {
            this.value = value;
        }
    }

    private final AtomicReference<Node<T>> top = new AtomicReference<>();

    public void push(T item) {
        Node<T> newNode = new Node<>(item);
        Node<T> oldTop;
        do {
            oldTop = top.get();
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode)); // CAS 更新栈顶
    }

    public T pop() {
        Node<T> oldTop;
        Node<T> newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) {
                return null; // 空栈
            }
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop)); // CAS 更新栈顶
        return oldTop.value;
    }
}

2. ABA 问题如何发生?

假设栈初始状态:

css 复制代码
top -> A -> B -> C

线程 1 执行 pop()

  1. 读取 oldTop = A,准备 CAS 更新栈顶为 B
  2. 被挂起(还未执行 CAS)。

线程 2 执行两次 pop()

  1. 弹出 A,栈变为 B -> C
  2. 弹出 B,栈变为 C
  3. push(A),栈变回 A -> C

线程 1 恢复执行 : • 执行 top.compareAndSet(A, B),发现 top 仍然是 A(ABA 发生!)。 • CAS 错误地成功 ,导致栈顶被设为 B(但 B 已经被弹出过,可能已被其他线程修改)。

最终栈状态

css 复制代码
top -> B   // 但 B 的 next 可能已经失效(如指向一个已删除的节点)

问题

• 栈结构被破坏,可能导致后续操作出错(如 NullPointerException)。 • 即使值看起来没变(ABA),但中间状态已被篡改。


3. 解决方案:AtomicStampedReference

通过 版本号(Stamp) 跟踪栈顶变化:

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

class SafeConcurrentStack<T> {
    private static class Node<T> {
        final T value;
        Node<T> next;

        Node(T value) {
            this.value = value;
        }
    }

    private final AtomicStampedReference<Node<T>> top = 
        new AtomicStampedReference<>(null, 0);

    public void push(T item) {
        Node<T> newNode = new Node<>(item);
        int[] stampHolder = new int[1];
        Node<T> oldTop;
        do {
            oldTop = top.get(stampHolder); // 获取当前栈顶和版本号
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode, stampHolder[0], stampHolder[0] + 1));
    }

    public T pop() {
        int[] stampHolder = new int[1];
        Node<T> oldTop;
        Node<T> newTop;
        do {
            oldTop = top.get(stampHolder);
            if (oldTop == null) {
                return null;
            }
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop, stampHolder[0], stampHolder[0] + 1));
        return oldTop.value;
    }
}

关键改进 : • 每次修改栈顶时,版本号(Stamp)递增。 • CAS 不仅比较引用,还比较版本号,确保中间未被其他线程修改。


4. 为什么余额修改不需要担心 ABA?

在余额修改场景(如 AtomicInteger):

java 复制代码
AtomicInteger balance = new AtomicInteger(100);

// 线程1:读取 balance=100,准备扣款 50(预期余额 100)
int oldBalance = balance.get();
int newBalance = oldBalance - 50;

// 线程2:临时修改 balance 为 200,又改回 100
balance.set(200);
balance.set(100);

// 线程1:执行 CAS(100 → 50)
balance.compareAndSet(100, 50); // 仍然成功

为什么没问题?

• 余额从 100200100,虽然值相同,但业务逻辑上 没有副作用 : • 只要最终余额正确(100 - 50 = 50),中间变化不影响结果。 • ABA 问题仅在数据结构依赖中间状态时才有害(如栈的节点指针被篡改)。


5. 总结

场景 是否需要解决 ABA 问题? 推荐工具
无锁栈/队列 ✅ 需要 AtomicStampedReference
余额修改(单变量) ❌ 不需要 AtomicInteger
状态机/版本控制 ✅ 需要 AtomicStampedReference

关键结论

  1. ABA 问题在无锁数据结构中更严重(如栈、队列、链表),因为指针可能被篡改。
  2. 余额修改等单变量操作通常不受影响,因为只关心最终值。
  3. AtomicStampedReference 是解决 ABA 问题的标准方案,通过版本号跟踪变化。
相关推荐
佚名涙3 小时前
go中锁的入门到进阶使用
开发语言·后端·golang
草捏子8 小时前
从CPU原理看:为什么你的代码会让CPU"原地爆炸"?
后端·cpu
嘟嘟MD8 小时前
程序员副业 | 2025年3月复盘
后端·创业
胡图蛋.8 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
无责任此方_修行中8 小时前
关于 Node.js 原生支持 TypeScript 的总结
后端·typescript·node.js
吃海鲜的骆驼9 小时前
SpringBoot详细教程(持续更新中...)
java·spring boot·后端
迷雾骑士9 小时前
SpringBoot中WebMvcConfigurer注册多个拦截器(addInterceptors)时的顺序问题(二)
java·spring boot·后端·interceptor
uhakadotcom10 小时前
Thrift2: HBase 多语言访问的利器
后端·面试·github
Asthenia041210 小时前
Java 类加载规则深度解析:从双亲委派到 JDBC 与 Tomcat 的突破
后端
方圆想当图灵10 小时前
从 Java 到 Go:面向对象的巨人与云原生的轻骑兵
后端·代码规范