cas是java实现无锁数据结构的核心原语,通过atomicreference等原子类的compareandset方法,利用cpu硬件指令保证原子性;适用于高竞争低延迟场景,典型应用包括无锁栈与spsc队列,需注意aba问题、内存可见性及循环重试等关键细节。

CAS(Compare-and-Swap)是 Java 中实现无锁(lock-free)数据结构的核心原语,主要通过 java.util.concurrent.atomic 包中的原子类(如 AtomicReference、AtomicInteger)暴露的 compareAndSet 方法完成。它不依赖 synchronized 或 ReentrantLock,而是靠 CPU 硬件指令保证操作的原子性,适合高竞争、低延迟场景。
用 CAS 实现无锁栈(Lock-Free Stack)
经典无锁栈基于“头插法”+ 原子更新 head 引用。每个节点包含数据和指向下一个节点的引用,栈顶由 AtomicReference<node></node> 维护。
关键点:
- push 操作:构造新节点 → 读取当前 top → 设置新节点 next 指向 top → CAS 更新 top 为新节点;失败则重试
- pop 操作:读取当前 top → 若为空返回 null;否则读取 top.next → CAS 尝试将 top 从旧 top 更新为 top.next;失败则重试
- 必须处理 ABA 问题:若 top 被弹出后又被相同地址节点重新压入(例如对象复用),CAS 可能误成功。Java 8+ 可用
AtomicStampedReference加版本号规避
用 CAS 实现无锁单生产者单消费者队列(SPSC)
真正高效、实用的无锁队列通常限于 SPSC 场景(如 LMAX Disruptor)。多生产者多消费者(MPMC)需更复杂设计(如带哨兵节点、双 CAS 或数组索引+padding 防伪共享)。
简易环形缓冲区 SPSC 队列要点:
- 用
AtomicInteger维护 head(消费者读位置)和 tail(生产者写位置) - 入队:先获取当前 tail → 计算槽位索引 → CAS 更新 tail;成功后写入元素;失败则重试
- 出队:类似,先 CAS 更新 head,再读取对应槽位元素
- 注意内存可见性:CAS 本身具有 volatile 语义,但读取数组元素前需确保该位置已被写入(通常靠 CAS 的 happens-before 规则保障)
避免常见陷阱
无锁编程极易出错,几个关键细节不能忽略:
- 永远在循环中调用 CAS:失败不抛异常,而是重读状态后重试(即 “乐观重试”)
- 节点引用不能直接置 null:pop 后的节点仍可能被其他线程观察到,应保持 next 引用不变,避免悬空指针或 GC 干扰
- 避免对象复用导致 ABA:尤其在对象池场景下,优先使用带 stamp 的原子引用,或改用不可变节点
- 慎用普通 volatile 字段替代 CAS:volatile 只保证可见性,不保证复合操作(如 read-modify-write)原子性
实际开发中更推荐的方式
手写无锁栈/队列难度高、易出错,JDK 已提供成熟实现:
-
ConcurrentLinkedQueue:基于 CAS 的无锁 MPMC 链表队列(含哨兵节点与松弛删除) -
ConcurrentLinkedDeque:无锁双端队列 -
java.util.concurrent.locks.StampedLock虽非无锁,但在读多写少时性能接近无锁 - 如需极致性能且场景受限(如 SPSC),可考虑 JCTools 库中的
MpscArrayQueue等经过充分测试的无锁队列
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











