Java 并发原子类完全指南:Atomic 全家桶、CAS/JMM、ABA、LongAdder、源码阅读路线与经典实战

张开发
2026/4/9 6:51:12 15 分钟阅读

分享文章

Java 并发原子类完全指南:Atomic 全家桶、CAS/JMM、ABA、LongAdder、源码阅读路线与经典实战
多线程编程中count这样简单的操作都不是线程安全的。用synchronized能解决问题但锁会带来阻塞和上下文切换开销。java.util.concurrent.atomic包提供了一套基于 CASCompare-And-Swap的无锁并发工具在“单变量竞争”场景下能写出既简洁又高性能的并发代码。本文从“Atomic 全家桶有哪些、怎么用、怎么选”切入深入讲清 CAS 自旋与线性化点、JMM/happens-before 语义、引用更新中的 ABA 问题及其解决对比AtomicLong与LongAdder在热点计数下的性能取舍并给出源码阅读路线、常见错误写法对照与实战代码。适用于 Java 与 Android 开发者。1. 原子类解决什么问题多线程下“读-改-写”不是原子操作count会丢更新。锁能解决但会带来阻塞与上下文切换成本。Atomic 通过 CAS 在大量“单变量竞争”场景下提供无锁的正确更新。2.java.util.concurrent.atomic全家桶清单速查表这些类属于JDK 标准库Java 可用Android 也提供差异多在版本与底层实现细节。2.1 基础数值/布尔类名说明AtomicBoolean原子布尔值AtomicInteger原子整型AtomicLong原子长整型2.2 引用与数组类名说明AtomicReferenceV原子引用AtomicReferenceArrayE原子引用数组AtomicIntegerArray原子整型数组AtomicLongArray原子长整型数组2.3 ABA 防护引用增强类名说明AtomicStampedReferenceV引用 版本号stampAtomicMarkableReferenceV引用 标记位mark2.4 字段更新器对对象字段做 CAS类名说明AtomicIntegerFieldUpdaterT整型字段更新器AtomicLongFieldUpdaterT长整型字段更新器AtomicReferenceFieldUpdaterT,V引用字段更新器2.5 高并发累加/归约类名说明LongAdder/DoubleAdder高并发累加器LongAccumulator/DoubleAccumulator高并发自定义归约3. Atomic 最常用 API记住这些就够用操作类型方法读写get()/set(x)CAScompareAndSet(expect, update)数值增减incrementAndGet()/getAndIncrement()/addAndGet(delta)弱写lazySet(x)后文解释4. 核心原理CAS、自旋与线性化点4.1 CAS 是什么CASCompare-And-Set当且仅当当前值等于期望值expect时原子更新为update并返回成功/失败。4.2 CAS 循环长什么样for(;;){intprevatomic.get();intnextprev1;if(atomic.compareAndSet(prev,next)){returnnext;}}4.3 ASCII 示意图线性化点在哪里Atomic 操作对外看起来“像瞬间发生”那个瞬间就是CAS 成功线程A: 读 prev10 计算 next11 CAS(10-11) 成功 ← 线性化点 线程B: 读 prev10 计算 next11 CAS(10-11) 失败 重试...5. 关键补全JMM / happens-before /lazySet的正确直觉5.1get/set可类比volatile读写原子类内部的值通常是volatile字段因此get()≈volatile读可见性set(x)≈volatile写可见性 必要的有序性5.2lazySet(x)最终可见但不承诺立刻适合“发布型写入”不要求其他线程马上看到通过更弱的写入语义降低开销经验拿不准就用set业务代码里lazySet往往不是必需品6. 经典场景与代码可直接复用6.1 只执行一次AtomicBooleanimportjava.util.concurrent.atomic.AtomicBoolean;publicclassOnce{privatefinalAtomicBooleandonenewAtomicBoolean(false);publicvoidrunOnce(Runnabler){if(done.compareAndSet(false,true)){r.run();}}}6.2 并发计数器AtomicLongimportjava.util.concurrent.atomic.AtomicLong;publicclassCounter{privatefinalAtomicLongcountnewAtomicLong();publiclonginc(){returncount.incrementAndGet();}publiclongadd(longdelta){returncount.addAndGet(delta);}publiclongvalue(){returncount.get();}}6.3 配置/状态整体替换AtomicReference 不可变对象importjava.util.concurrent.atomic.AtomicReference;publicclassConfigHolder{staticfinalclassConfig{finalinttimeoutMs;finalbooleanenabled;Config(inttimeoutMs,booleanenabled){this.timeoutMstimeoutMs;this.enabledenabled;}}privatefinalAtomicReferenceConfigrefnewAtomicReference(newConfig(1000,true));publicConfigget(){returnref.get();}publicvoidupdate(inttimeoutMs,booleanenabled){ref.set(newConfig(timeoutMs,enabled));}}6.4 Atomic CAS 状态机通用importjava.util.concurrent.atomic.AtomicInteger;publicclassTaskStateMachine{privatestaticfinalintNEW0;privatestaticfinalintRUNNING1;privatestaticfinalintSUCCESS2;privatestaticfinalintFAILED3;privatefinalAtomicIntegerstatenewAtomicInteger(NEW);publicbooleanstart(){returnstate.compareAndSet(NEW,RUNNING);}publicbooleansucceed(){returnstate.compareAndSet(RUNNING,SUCCESS);}publicbooleanfail(){returnstate.compareAndSet(RUNNING,FAILED);}publicintgetState(){returnstate.get();}}7. 关键陷阱ABA配图 解决7.1 ABA 是什么CAS 只看“现在是不是 A”。如果经历 A→B→ACAS 仍会认为“没变过”。7.2 ASCII 示意图ABA 的“历史被抹掉”t0: 内存值 A 线程1: 读到 A准备 CAS(A-C) 线程2: 把 A 改成 B 线程2: 又把 B 改回 A 线程1: CAS(A-C) 成功 ← 线程1以为“期间没变”但其实变过7.3AtomicStampedReference用版本号解决importjava.util.concurrent.atomic.AtomicStampedReference;publicclassAbaFix{privatefinalAtomicStampedReferenceStringrefnewAtomicStampedReference(A,0);publicbooleanupdateAtoB(){int[]stampHoldernewint[1];Stringvref.get(stampHolder);intstampstampHolder[0];returnref.compareAndSet(v,B,stamp,stamp1);}}7.4AtomicMarkableReference标记位更轻当你只需要“是否删除/是否有效”的布尔标记用 mark 就够了。8. 性能关键点AtomicLongvsLongAdder热点计数必看8.1AtomicLong是单点竞争所有线程 - CAS 同一个 value 竞争越大 - 失败越多 - 自旋越多 - 越慢8.2LongAdder分片降低冲突线程们分散更新多个 cell cell[0] cell[1] cell[2] ... 最后 sum() 求和示例importjava.util.concurrent.atomic.LongAdder;publicclassHotCounter{privatefinalLongAdderaddernewLongAdder();publicvoidinc(){adder.increment();}publiclongvalue(){returnadder.sum();}}结论监控/统计/埋点这类热点计数优先LongAdder需要强一致读写语义的场景AtomicLong更直接9. 字段更新器与原子数组什么时候用9.1Atomic*FieldUpdater减少包装对象开销importjava.util.concurrent.atomic.AtomicIntegerFieldUpdater;publicclassNode{volatileintstate;privatestaticfinalAtomicIntegerFieldUpdaterNodeSTATEAtomicIntegerFieldUpdater.newUpdater(Node.class,state);publicbooleantrySet(intexpect,intupdate){returnSTATE.compareAndSet(this,expect,update);}}9.2Atomic*Array分桶统计常用保证单个元素原子更新如果你要“一次更新多个桶并保持一致”仍需要更高层的设计锁/不可变整体等。10. 源码阅读路线最省时间版不同 JDK/Android 版本可能用Unsafe或VarHandle但语义一致。建议顺序AtomicInteger看value、compareAndSet、incrementAndGetCAS 循环AtomicReference引用 CAS 与 ABA 风险AtomicStampedReference/AtomicMarkableReference组合比较与更新LongAddercell 分片与sum()你只要抓三件事值字段是谁通常 volatileCAS 改的是哪个字段失败后如何重试/退避11. 常见错误写法 vs 正确写法强烈建议收藏区11.1 错误用volatile当原子自增volatileintcount0;publicvoidinc(){count;// 仍然不安全}正确二选一用原子类importjava.util.concurrent.atomic.AtomicInteger;AtomicIntegercountnewAtomicInteger();publicvoidinc(){count.incrementAndGet();}或者用锁保护复合操作当你还有别的共享状态要一起保护时更合适。11.2 错误想“多变量一起原子”却分别用多个 AtomicAtomicIntegeranewAtomicInteger();AtomicIntegerbnewAtomicInteger();publicvoidupdateBoth(){a.incrementAndGet();b.incrementAndGet();// 这两步之间可能被别的线程观察到“中间态”}正确思路常用两种用锁把临界区包起来最直观或者把 (a,b) 封装成不可变对象用AtomicReferenceState整体替换读多写少时很优雅示例整体替换importjava.util.concurrent.atomic.AtomicReference;publicclassPairState{staticfinalclassState{finalinta,b;State(inta,intb){this.aa;this.bb;}}privatefinalAtomicReferenceStaterefnewAtomicReference(newState(0,0));publicvoidincBoth(){for(;;){Stateprevref.get();StatenextnewState(prev.a1,prev.b1);if(ref.compareAndSet(prev,next)){return;}}}publicStateget(){returnref.get();}}11.3 错误热点计数还在用AtomicLong高并发下退化AtomicLongqpsnewAtomicLong();publicvoidhit(){qps.incrementAndGet();// 高竞争下可能大量自旋失败}正确统计类计数importjava.util.concurrent.atomic.LongAdder;LongAdderqpsnewLongAdder();publicvoidhit(){qps.increment();}publiclongqps(){returnqps.sum();}12. 选型总结速查表场景推荐选择只要可见性volatile单变量原子更新AtomicBoolean/AtomicInteger/AtomicLong热点计数高并发统计LongAdder整体替换状态/配置AtomicReference不可变对象担心 ABA 问题AtomicStampedReference/AtomicMarkableReference大量对象字段要原子操作Atomic*FieldUpdater分桶/数组统计Atomic*Array13. 结语Atomic 的关键不是“背 API”而是掌握三件事CAS 成功就是生效点、JMM 可见性语义要有直觉、以及高竞争下懂得换LongAdder或改变结构。把问题收敛到“单点状态”时Atomic 往往能写出既简洁又高性能的并发代码。文末小贴士1 分钟速查清单CAScompareAndSet(expect, update)成功那一刻就是操作生效点JMMget()有 volatile 读语义set()有 volatile 写语义ABA担心就上AtomicStampedReference带版本号热点计数高并发下LongAdder完胜AtomicLong多变量原子要么用锁要么用AtomicReference包不可变对象状态机用AtomicInteger配合常量状态值CAS 驱动转移相关推荐深入理解 AQS 和 CAS 原理Synchronized 和 ReentrantLock

更多文章