cas 本身不提供可见性,必须作用于 volatile 字段才能读到最新值并实现正确比较交换;volatile 保障可见性与有序性,cas 保障原子性,二者协同构成无锁并发基石。

CAS 机制本身不提供 volatile 语义,它依赖 volatile 变量作为操作目标,才能正确读取最新值并实现“比较+交换”的原子逻辑。换句话说,CAS 不是靠自己保证可见性,而是必须作用在 volatile 字段上,才能让整个无锁流程成立。
volatile 是 CAS 能“看到最新值”的前提
CAS 操作要判断“当前内存值是否等于预期值”,这个“当前内存值”必须是其他线程刚写入的最新结果。如果字段没用 volatile 修饰:
- 线程可能从自己的工作内存(CPU缓存)读取旧值,导致 compare 失败或漏判更新;
- 即使 CAS 成功写入新值,也不保证立即刷回主内存,其他线程下次读仍可能看到过期值;
- 编译器或 CPU 还可能对 CAS 前后的读写指令重排序,破坏执行逻辑顺序。
Java 内存模型中的协作契约
在 JUC 实现中,volatile 和 CAS 构成一套隐式契约:
-
volatile 字段负责“广播”状态变更:比如 AQS 中的
volatile int state、ConcurrentLinkedQueue 中的volatile Node tail,它们不直接参与计算,但确保所有线程读到的是同一份事实; -
CAS 操作负责“验证并提交”状态跃迁:每次调用
compareAndSet(),底层都先用 volatile 语义读取当前值,再发起原子比较交换; - volatile 的内存屏障天然支撑 CAS 自旋:写 volatile 变量会插入 StoreLoad 屏障,防止后续 CAS 读被重排到写之前;读 volatile 变量会插入 LoadLoad 屏障,确保前面的读已完成,为下一次 compare 提供可靠快照。
典型源码印证:AtomicInteger.incrementAndGet()
它的底层是循环调用 compareAndSet():
- 每次循环开始前,都通过 volatile 读获取当前
value(即最新值); - 然后用该值作为期望值,尝试 CAS 更新;
- 若失败,说明有其他线程已修改,就重新读 volatile 值,再试——这个“重读”动作,全靠 volatile 语义保障及时性和一致性。
没有 volatile 的 CAS 会怎样?
假设把 AtomicInteger.value 声明成普通 int:
- 多个线程可能长期各自缓存旧值,CAS 总是拿自己缓存的“幻觉值”去比,永远无法成功;
- 即使某次侥幸成功,新值也可能滞留在某个 CPU 缓存里,别的线程看不到,结构状态实际已分裂;
- 整个无锁算法退化为不可预测、不可终止的随机行为,失去 lock-free 的本质意义。











