
上一篇用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。共享值没有变化时提交成功值已被其他线程更新时这次尝试失败回到读取步骤。一个典型循环包含四步读取当前值检查业务前提根据当前值计算新值CAS提交失败后重新开始。失败不代表程序异常它说明本次读取已经过期。重新尝试时必须重新检查前提不能沿用旧值计算出的结果。三、完整示例计数与有条件扣减保存为AtomicCasDemo.java使用JDK21运行importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.atomic.AtomicInteger;publicclassAtomicCasDemo{staticbooleanreserve(AtomicIntegerremaining,intamount){for(;;){intcurrentremaining.get();if(currentamount){returnfalse;}if(remaining.compareAndSet(current,current-amount)){returntrue;}}}publicstaticvoidmain(String[]args)throwsException{AtomicIntegercounternewAtomicInteger();try(ExecutorServicepoolExecutors.newFixedThreadPool(4)){for(intworker0;worker4;worker){pool.submit(()-{for(inti0;i25_000;i){counter.incrementAndGet();}});}}System.out.println(countercounter.get());AtomicIntegerremainingnewAtomicInteger(7);System.out.println(reserve 3reserve(remaining,3));System.out.println(remainingremaining.get());System.out.println(reserve 5reserve(remaining,5));System.out.println(remainingremaining.get());if(counter.get()!100_000||remaining.get()!4){thrownewAssertionError(unexpected result);}}}javac AtomicCasDemo.javajavaAtomicCasDemo输出counter100000 reserve 3true remaining4 reserve 5false remaining4这段代码已在本机JDK21编译运行。ExecutorService.close()会等待已提交任务结束因此主线程随后读到100000。第二部分先成功预留3个名额剩余4个再申请5个时业务检查直接返回false。reserve()中的条件检查必须放在重试循环里。如果CAS失败说明current已经失效下一轮重新读取后库存可能已经不足也可能仍可满足申请。四、高阶更新方法也可能重复执行函数updateAndGet和accumulateAndGet可以把CAS重试封装起来。例如intupdatedcounter.updateAndGet(value-value2);这些方法简洁但传入的函数可能因竞争而被重新应用。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原子包说明。七、 思维导图AtomicInteger与CAS问题来源复合更新会丢失volatile不合并操作CAS循环读取并计算比较提交与失败重试常用方法原子递增函数式更新适用边界单个共享值多字段改用整体协调八、总结总结要点原子复合更新解决了普通i可能丢失的问题。AtomicInteger把单个int的读取、比较与写入放进明确的原子API中。CAS循环依靠期望值判断这次计算是否仍然有效。提交失败后重新读取、重新检查条件再计算候选新值才能保持业务前提成立。单变量边界决定了它适合处理什么。多个字段、外部副作用、条件等待和持久化一致性需要更完整的状态设计与协调手段。下一篇继续讨论LongAdder看看高竞争统计如何把更新压力拆到多个单元再在读取时汇总。如果你觉得这篇文章对你有所帮助欢迎点赞、收藏、分享