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 问题的标准方案,通过版本号跟踪变化。
相关推荐
掘了16 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法16 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
Cobyte17 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行18 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple18 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东18 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
invicinble19 小时前
springboot的核心实现机制原理
java·spring boot·后端
全栈老石19 小时前
Python 异步生存手册:给被 JS async/await 宠坏的全栈工程师
后端·python
space621232719 小时前
在SpringBoot项目中集成MongoDB
spring boot·后端·mongodb