上一篇用StampedLock完成了“先读取、再校验”的乐观读取。把视角转到单个共享值,更新也可以遵循类似思路:先读取当前值,基于它计算新值,再确认提交时当前值仍然没有变化。
AtomicInteger把这套原子读写能力封装成了常用API。本文基于Java 21,从计数器和库存扣减两个例子理解CAS循环,同时划清单变量原子更新的适用边界。示例只验证正确结果,不提供未经测量的性能结论。
一、i++为什么会丢失更新
普通int的i++看起来只有一行,实际包含读取、加一和写回。两个线程可能同时读到10,都计算出11,再分别写回11;执行了两次递增,结果只增加一次。
给字段加上volatile可以让读写具有相应的可见性和顺序语义,但不会把“读取后计算再写回”合并成一次不可分割的操作。需要保护复合更新时,可以使用锁,也可以在适合的单变量场景中使用java.util.concurrent.atomic包。
AtomicInteger维护一个可原子更新的int值。incrementAndGet()返回递增后的值,getAndIncrement()返回递增前的值;选择方法时要先看调用者需要哪一个结果。完整API见Java 21 AtomicInteger文档。
二、CAS比较的是“我刚才看到的值”
compareAndSet(expectedValue, newValue)可以理解为:当前值仍等于期望值时,原子地写入新值并返回true;如果其他线程已经改过,返回false,调用者重新读取并计算。

图中导师猫拿着读取到的expected凭据,学习猫准备提交newValue。共享值没有变化时提交成功;值已被其他线程更新时,这次尝试失败,回到读取步骤。
一个典型循环包含四步:
失败不代表程序异常,它说明本次读取已经过期。重新尝试时必须重新检查前提,不能沿用旧值计算出的结果。
三、完整示例:计数与有条件扣减
保存为AtomicCasDemo.java,使用JDK21运行:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCasDemo {
static boolean reserve(AtomicInteger remaining, int amount) {
for (;;) {
int current = remaining.get();
if (current < amount) {
return false;
}
if (remaining.compareAndSet(current, current – amount)) {
return true;
}
}
}
public static void main(String[] args) throws Exception {
AtomicInteger counter = new AtomicInteger();
try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
for (int worker = 0; worker < 4; worker++) {
pool.submit(() -> {
for (int i = 0; i < 25_000; i++) {
counter.incrementAndGet();
}
});
}
}
System.out.println("counter=" + counter.get());
AtomicInteger remaining = new AtomicInteger(7);
System.out.println("reserve 3=" + reserve(remaining, 3));
System.out.println("remaining=" + remaining.get());
System.out.println("reserve 5=" + reserve(remaining, 5));
System.out.println("remaining=" + remaining.get());
if (counter.get() != 100_000 || remaining.get() != 4) {
throw new AssertionError("unexpected result");
}
}
}
javac AtomicCasDemo.java
java AtomicCasDemo
输出:
counter=100000
reserve 3=true
remaining=4
reserve 5=false
remaining=4
这段代码已在本机JDK21编译运行。ExecutorService.close()会等待已提交任务结束,因此主线程随后读到100000。第二部分先成功预留3个名额,剩余4个;再申请5个时,业务检查直接返回false。
reserve()中的条件检查必须放在重试循环里。如果CAS失败,说明current已经失效;下一轮重新读取后,库存可能已经不足,也可能仍可满足申请。
四、高阶更新方法也可能重复执行函数
updateAndGet和accumulateAndGet可以把CAS重试封装起来。例如:
int updated = counter.updateAndGet(value -> value + 2);
这些方法简洁,但传入的函数可能因竞争而被重新应用。Oracle文档要求更新函数无副作用,因为竞争会导致函数多次执行。不要在函数里发送消息、写文件、扣第三方余额或产生一次性编号;即使某次计算没有成功提交,副作用也已经发生。
纯计算适合放进更新函数:输入只来自当前值,输出只表示候选新值。涉及外部动作时,可以先完成原子状态转换,再根据最终成功结果执行后续流程,同时考虑失败恢复和幂等设计。
五、原子的是一个值,不等于业务整体原子
假设账户有balance与frozen两个AtomicInteger。分别更新两个对象,只能保证每一次单变量操作原子,不能保证其他线程看不到一新一旧的组合。
同样,下面的“先检查后递增”也跨越了两次独立操作:
if (counter.get() < limit) {
counter.incrementAndGet();
}
多个线程可能同时通过检查,让结果超过limit。可以把检查和写入放进CAS循环;如果业务状态包含多个字段,可把相关字段封装成一个不可变对象并用AtomicReference整体替换,或直接使用锁、数据库事务等更合适的协调机制。
AtomicInteger也不自动提供等待条件、公平排队或跨进程一致性。单机内存里的原子更新无法替代数据库约束,也不能替你处理服务重启和重复请求。
六、竞争、重试与方法选择
CAS避免了为一次简单更新显式获取互斥锁,但竞争激烈时,多个线程会反复读取和重试,依然要消耗处理器时间。是否更快取决于竞争程度、更新逻辑和运行环境,需要用真实负载测量。
| 取得唯一递增序号 | AtomicInteger或AtomicLong的getAndIncrement |
| 基于旧值做纯函数更新 | updateAndGet、accumulateAndGet |
| 需要条件检查并更新单个值 | 明确的CAS循环 |
| 高频统计,读取总和不要求原子快照 | 后续分析LongAdder |
| 多字段约束、条件等待或复杂临界区 | 锁、不可变状态整体替换或事务 |
Java 21的原子类通过相应的VarHandle访问模式描述内存语义。日常业务代码优先使用语义清楚的get()、set()、compareAndSet()和原子算术方法;在没有明确并发设计与验证前,不要为了“更弱可能更快”随意切换到Plain、Opaque、Acquire或Release变体。java.util.concurrent.atomic包的定位和边界可对照Java 21原子包说明。
七、🧠 思维导图
#mermaid-svg-6arLJqGnTTXUMuba{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6arLJqGnTTXUMuba .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6arLJqGnTTXUMuba .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6arLJqGnTTXUMuba .error-icon{fill:#552222;}#mermaid-svg-6arLJqGnTTXUMuba .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6arLJqGnTTXUMuba .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6arLJqGnTTXUMuba .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6arLJqGnTTXUMuba .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6arLJqGnTTXUMuba .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6arLJqGnTTXUMuba .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6arLJqGnTTXUMuba .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6arLJqGnTTXUMuba .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6arLJqGnTTXUMuba .marker.cross{stroke:#333333;}#mermaid-svg-6arLJqGnTTXUMuba svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6arLJqGnTTXUMuba p{margin:0;}#mermaid-svg-6arLJqGnTTXUMuba .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6arLJqGnTTXUMuba .cluster-label text{fill:#333;}#mermaid-svg-6arLJqGnTTXUMuba .cluster-label span{color:#333;}#mermaid-svg-6arLJqGnTTXUMuba .cluster-label span p{background-color:transparent;}#mermaid-svg-6arLJqGnTTXUMuba .label text,#mermaid-svg-6arLJqGnTTXUMuba span{fill:#333;color:#333;}#mermaid-svg-6arLJqGnTTXUMuba .node rect,#mermaid-svg-6arLJqGnTTXUMuba .node circle,#mermaid-svg-6arLJqGnTTXUMuba .node ellipse,#mermaid-svg-6arLJqGnTTXUMuba .node polygon,#mermaid-svg-6arLJqGnTTXUMuba .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6arLJqGnTTXUMuba .rough-node .label text,#mermaid-svg-6arLJqGnTTXUMuba .node .label text,#mermaid-svg-6arLJqGnTTXUMuba .image-shape .label,#mermaid-svg-6arLJqGnTTXUMuba .icon-shape .label{text-anchor:middle;}#mermaid-svg-6arLJqGnTTXUMuba .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6arLJqGnTTXUMuba .rough-node .label,#mermaid-svg-6arLJqGnTTXUMuba .node .label,#mermaid-svg-6arLJqGnTTXUMuba .image-shape .label,#mermaid-svg-6arLJqGnTTXUMuba .icon-shape .label{text-align:center;}#mermaid-svg-6arLJqGnTTXUMuba .node.clickable{cursor:pointer;}#mermaid-svg-6arLJqGnTTXUMuba .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6arLJqGnTTXUMuba .arrowheadPath{fill:#333333;}#mermaid-svg-6arLJqGnTTXUMuba .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6arLJqGnTTXUMuba .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6arLJqGnTTXUMuba .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6arLJqGnTTXUMuba .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6arLJqGnTTXUMuba .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6arLJqGnTTXUMuba .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6arLJqGnTTXUMuba .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6arLJqGnTTXUMuba .cluster text{fill:#333;}#mermaid-svg-6arLJqGnTTXUMuba .cluster span{color:#333;}#mermaid-svg-6arLJqGnTTXUMuba div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6arLJqGnTTXUMuba .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6arLJqGnTTXUMuba rect.text{fill:none;stroke-width:0;}#mermaid-svg-6arLJqGnTTXUMuba .icon-shape,#mermaid-svg-6arLJqGnTTXUMuba .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6arLJqGnTTXUMuba .icon-shape p,#mermaid-svg-6arLJqGnTTXUMuba .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6arLJqGnTTXUMuba .icon-shape .label rect,#mermaid-svg-6arLJqGnTTXUMuba .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6arLJqGnTTXUMuba .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6arLJqGnTTXUMuba .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6arLJqGnTTXUMuba :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
AtomicInteger与CAS
问题来源
复合更新会丢失
volatile不合并操作
CAS循环
读取并计算
比较提交与失败重试
常用方法
原子递增
函数式更新
适用边界
单个共享值
多字段改用整体协调
八、总结
总结要点
原子复合更新解决了普通i++可能丢失的问题。AtomicInteger把单个int的读取、比较与写入放进明确的原子API中。
CAS循环依靠期望值判断这次计算是否仍然有效。提交失败后重新读取、重新检查条件,再计算候选新值,才能保持业务前提成立。
单变量边界决定了它适合处理什么。多个字段、外部副作用、条件等待和持久化一致性,需要更完整的状态设计与协调手段。
下一篇继续讨论LongAdder,看看高竞争统计如何把更新压力拆到多个单元,再在读取时汇总。
👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊




