
AtomicReference 实现无锁栈的核心思想
无锁栈的关键在于不使用 synchronized 或 ReentrantLock,而是依靠 AtomicReference 的 compareAndSet(CAS)原子操作来保证多线程下栈顶节点的更新安全。栈结构由链表构成,每个节点包含数据和指向下一个节点的引用,栈顶由一个 AtomicReference 原子地维护。所有修改都围绕“预期旧值 → 尝试设置新值”这一 CAS 循环展开,失败则重试,直到成功。
Push 操作:头插法 + CAS 循环
压栈即在链表头部插入新节点。逻辑是:读取当前栈顶 node,构造新节点使其 next 指向该 node,再用 CAS 尝试将栈顶更新为新节点。
若 CAS 成功,说明期间无人修改栈顶,插入完成 若 CAS 失败,说明其他线程已抢先更新了 top,需重新读取最新 top,再次构造并重试 典型实现中会用 while(true) 循环包裹 CAS,确保最终成功
Pop 操作:头删 + CAS 循环 + ABA 问题应对
弹栈需取出栈顶节点并更新 top 指向其 next。同样依赖 CAS:记录当前 top 和 top.next,尝试将 top 从旧值更新为 top.next。
CAS 成功则返回原栈顶节点的数据,操作完成 CAS 失败则重读 top,继续循环 注意潜在的 ABA 问题:top 被改为 B 又改回 A,CAS 误判为未变。实际生产中可结合 AtomicStampedReference 增加版本戳,但纯 AtomicReference 实现通常假设栈元素不会被重复回收复用(如使用对象池时需额外防护)
关键细节与常见陷阱
无锁栈看似简洁,但几个细节极易出错:
Node 类必须是不可变的 next 引用(或至少 next 在构造后不被外部修改),否则破坏链表一致性 Pop 返回 null 表示栈空,但需注意:即使 top 为 null,CAS 设置为 null 仍可能失败(因有竞争),所以判断空栈应以读取到的 top == null 为准,而非依赖某次 CAS 结果 不能在 CAS 循环内做耗时操作(如 I/O、复杂计算),否则导致重试开销大、吞吐下降 内存可见性由 AtomicReference 的 volatile 语义保障,无需额外同步

评论(0)