CAS 实现无锁栈的核心是用乐观重试替代互斥锁,依赖 AtomicReference 的 compareAndSet 及 final 字段语义保障线程安全与可见性,需防护空栈、ABA 和自旋开销。

CAS 在并发环境中实现高效无锁栈,核心是用乐观重试替代互斥锁,靠硬件级原子指令保障栈顶指针更新的线程安全。它不阻塞线程,也不引入上下文切换,特别适合高吞吐、短临界区的场景。
用 AtomicReference 管理栈顶指针
栈操作本质是“读当前顶点 → 构造新节点 → 更新顶点”三步,普通引用赋值无法保证原子性。AtomicReference 提供 compareAndSet 方法,底层调用 CPU 的 cmpxchg 指令,在缓存行级别完成比较并交换,天然具备原子性与可见性。
- 每次 push/pop 都以 top.get() 获取当前栈顶,再用 CAS 尝试更新——成功即退出,失败就重试
- 必须检查 CAS 返回值,忽略 false 会导致数据丢失
- 不能加 synchronized,否则违背无锁设计初衷,也失去性能优势
确保节点构造与发布的安全性
新节点的 next 字段必须对其他线程立即可见,否则可能看到半初始化状态。JVM 不保证构造器内字段写入的跨线程可见性,单独赋值存在风险。
- 将 next 声明为 final,并在构造器中一次性赋值:Node(E item, Node
next) { this.item = item; this.next = next; } - 利用 Java 内存模型的 final field semantics,让新节点发布即安全
- 避免在构造后修改 next,也不需额外 volatile 或同步
pop 操作要防空栈与自旋失控
出栈看似简单,但实际面临空栈无限循环、ABA 效应、CPU 空转等陷阱,需主动防护。
- 先调用 top.get() 判断是否为 null,再进入 CAS 循环;否则空栈时会死循环
- 循环体内推荐插入 Thread.onSpinWait()(Java 9+),提示 CPU 当前处于自旋等待,降低功耗
- 链表中间节点无需任何同步修饰——所有变更都通过 top 的 CAS 完成,访问路径唯一
不依赖锁,但需理解它的代价
无锁不等于零开销。CAS 成功时极轻量,失败时靠自旋重试,高竞争下可能消耗大量 CPU。
- 适合读多写少、操作粒度小的场景(如 Treiber Stack)
- 若竞争持续激烈,可考虑退化策略或改用分段结构(如 LongAdder 对计数器的启发)
- ABA 问题虽不影响栈逻辑正确性,但可能触发 JVM unsafe 警告,必要时可用带版本号的 AtomicStampedReference










