CAS是实现无锁数据结构的核心机制,通过原子性“比较并交换”避免互斥锁,依赖乐观重试而非自旋锁,典型应用如无锁栈的push/pop操作均基于AtomicReference的循环CAS完成。

Java 中不能直接用“自旋锁(Spin Lock)”实现无锁数据结构,因为真正的无锁(lock-free)算法**必须避免任何线程因等待而空转消耗 CPU**——而 CAS 循环本身不是自旋锁,它是无锁的核心机制;所谓“CAS 循环”是线程在发现竞争时反复重试 CAS 操作,直到成功,属于乐观重试策略,不是加锁后的自旋等待。
理解 CAS 循环的本质
CAS(Compare-And-Swap)是 CPU 提供的原子指令,在 Java 中通过 Unsafe.compareAndSwapXXX 或更推荐的 AtomicXxx 类(如 AtomicInteger、AtomicReference) 封装使用。它的典型模式是:
- 读取当前值(expected)
- 基于该值计算新值(desired)
- 用 CAS 尝试更新:仅当当前值仍等于 expected 时才设为 desired
- 失败则重试(重新读取 → 计算 → CAS),形成“循环”
这个循环不是“锁住后自旋”,而是“无锁前提下的乐观重试”。它不阻塞、不挂起线程,也不需要 synchronized 或 Lock,因此符合 lock-free 定义。
手写无锁栈(Lock-Free Stack)示例
以经典的 Michael-Scott 无锁栈为例,核心是用 AtomicReference
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
static class Node<E> {
final E item;
Node<E> next;
Node(E item) { this.item = item; }
}
static class LockFreeStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> node = new Node<>(item);
Node<E> currentTop;
do {
currentTop = top.get(); // 读当前栈顶
node.next = currentTop; // 新节点指向原栈顶
} while (!top.compareAndSet(currentTop, node)); // CAS 更新栈顶
}
public E pop() {
Node<E> currentTop;
Node<E> newTop;
do {
currentTop = top.get();
if (currentTop == null) return null;
newTop = currentTop.next;
} while (!top.compareAndSet(currentTop, newTop));
return currentTop.item;
}
}
关键点:
- 每次 CAS 失败,说明其他线程已修改了 top,当前操作需基于最新值重试
- 没有 wait/notify、没有 synchronized、没有 Lock,纯靠原子读 + CAS 重试
- 即使多线程激烈竞争,也不会死锁或活锁(lock-free 要求:至少一个线程能进步)
何时需要控制“重试行为”,避免过度自旋
纯 CAS 循环在高竞争下可能持续重试,浪费 CPU。Java 不提供底层 pause 指令封装,但可适度缓解:
- 加入简单退避(如 Thread.onSpinWait(),JDK9+,提示 CPU 当前在自旋)
- 少量重试后让出 CPU(如 Thread.yield()),但要谨慎——yield 不保证效果,且破坏 lock-free 的严格保证
- 更稳妥的做法是:设计上减少竞争(如分段、缓存行对齐 @Contended)、或改用更高级的无等待(wait-free)结构
注意:插入 Thread.sleep(1) 或锁机制就不再是 lock-free,而是退化为阻塞式同步。
常见陷阱与注意事项
- A-B-A 问题:栈顶从 A→B→A,CAS 误判成功。可用 AtomicStampedReference 带版本号解决(如无锁队列中常用)
- 内存可见性依赖 volatile 语义:AtomicXxx 内部基于 volatile + Unsafe,确保重排序约束和跨核可见性
- ABA 不一定都是 bug:对栈/队列,只要逻辑正确(如节点复用安全),有时可忽略;但涉及资源释放(如内存回收)时必须处理
- 不要在循环里做耗时操作:CAS 循环应轻量,计算逻辑越简单越好,否则重试开销剧增

















