cas本质是硬件级原子指令,unsafe.getandaddint通过volatile读取内存值v、以该值为预期值a、以v+delta为新值b,用do-while自旋调用native cas直至成功,返回旧值v。

Java 中的 CAS(Compare-And-Swap)本质是一种硬件级原子指令,而 Unsafe.getAndAddInt 是它在 JVM 层最典型的封装实现——它用自旋 + volatile 读 + native CAS 的组合,把“读—判—改”三步压缩成逻辑上不可分割的原子操作。
CAS 的三个核心要素怎么对应到 getAndAddInt
方法签名:public final int getAndAddInt(Object obj, long offset, int delta)
它内部隐含了 CAS 的三元组:
-
内存值 V:通过
getIntVolatile(obj, offset)从对象指定偏移量处读取的当前值(volatile 保证可见性) -
预期值 A:就是刚读出的
v,代表“我假设此刻这个值还没被别人动过” -
要写入的值 B:即
v + delta,比如incrementAndGet()中的+1
整个操作的目标是:仅当内存中该位置的值仍等于 v 时,才把它设为 v + delta;否则重试。
自旋循环的结构和退出条件
源码中的 do-while 循环不是随意写的,它严格服务于“乐观尝试 + 失败重试”模型:
- 每次循环开头都重新读一次最新值(
v = getIntVolatile(...)),确保不基于过期快照做判断 - 循环体末尾调用
compareAndSwapInt(obj, offset, v, v + delta) - 退出条件只有一个:CAS 成功返回 true
- 只要 CAS 返回 false(说明期间有其他线程抢先修改了该内存位置),就回到循环开头,重新读、再试
也就是说,循环不会因“次数过多”或“等待超时”而退出,只会在成功更新后自然跳出,返回的是更新前的旧值 v。
为什么必须用 volatile 读 + native CAS 而非普通读
这两个配合缺一不可:
-
getIntVolatile保证每次读都绕过 CPU 缓存,直接从主内存(或一致性协议保障的最新副本)加载,避免线程看到陈旧值导致无限重试 -
compareAndSwapInt是 native 方法,最终映射到 CPU 的cmpxchg指令,由硬件保证“比较并写入”这一步本身不可中断、不可分割 - 如果换成普通
getInt,可能读到寄存器里的旧副本;如果 CAS 不是硬件指令,就无法规避 JVM 内存模型带来的重排序与可见性风险
一个典型执行场景(两个线程同时 increment)
假设初始值为 5:
- 线程 A 读到 v=5,准备写 6;线程 B 几乎同时读到 v=5,也准备写 6
- A 先执行 CAS(5→6),成功,内存变为 6,A 退出循环,返回 5
- B 执行 CAS(5→6) 时发现内存已是 6 ≠ 5,失败,于是重新读 —— 这次读到 v=6,再尝试 CAS(6→7)
- 只要没有第三个竞争者,B 下轮就会成功,返回 6
整个过程无锁、无阻塞、无上下文切换,代价是可能多次重试,但对多数低争用场景比 synchronized 更轻量。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











