atomicinteger 通过 cpu 级 cas 指令(如 cmpxchg)和 unsafe 直接内存操作实现线程安全,而非 synchronized;其 value 字段为 volatile 保证可见性,cas 内置内存屏障确保有序性与原子性。

AtomicInteger 为什么能保证线程安全,而不是靠 synchronized?
它靠的是 CPU 级别的原子指令(比如 cmpxchg),配合 Unsafe 类直接操作内存地址,绕过 JVM 的常规对象字段访问路径。Java 层面的 incrementAndGet() 看似简单,背后是 Unsafe.compareAndSwapInt() 的一次或多次重试——失败就重试,直到成功,这就是 CAS(Compare-And-Swap)。
常见错误现象:AtomicInteger 在高并发下性能远好于 synchronized,但一旦你把它当普通整数做复合操作(比如“先 get 再 set”),线程安全就立刻失效。
- 别写
ai.set(ai.get() + 1)—— 这不是原子的,中间可能被其他线程插队 - 要用
ai.incrementAndGet()、ai.addAndGet(5)、ai.updateAndGet(x -> x * 2)这类内置原子方法 -
getAndIncrement()和incrementAndGet()返回值不同,别用反了
Unsafe 是怎么被 AtomicInteger 用起来的?
Unsafe 不是公开 API,AtomicInteger 通过反射拿到它的单例实例,再用它定位到对象内 value 字段的内存偏移量(valueOffset)。后续所有 CAS、volatile 读写,都基于这个偏移量直接操作堆内存。
关键点:Unsafe 的 compareAndSwapInt() 方法接收四个参数:对象引用、字段偏移量、期望旧值、目标新值。它不关心 Java 对象模型,只认地址+值。
立即学习“Java免费学习笔记(深入)”;
- 字段偏移量在类加载时确定,且不会变;但不同 JVM(如 ZGC vs G1)、不同平台(x86 vs ARM)下偏移量可能不同
- JDK 9+ 开始限制
Unsafe的使用,反射获取会触发警告;JDK 17 默认禁止,需加--add-opens java.base/jdk.internal.misc=ALL-UNNAMED - 不要自己 new
Unsafe实例——它是单例,且构造私有;试图反射调用会抛SecurityException
为什么 value 字段是 volatile,但 CAS 还要额外加内存屏障?
volatile 保证了每次 get() 都从主存读、每次 set() 都立即刷回主存,但它不保证复合操作的原子性。而 CAS 操作本身隐含了内存屏障:读操作前有 LoadLoad/LoadStore 屏障,写操作后有 StoreStore/LoadStore 屏障,确保屏障两侧的内存访问不被重排序。
典型踩坑场景:你在 AtomicInteger 上做轮询等待(比如 while(ai.get() Thread.onSpinWait() 或适当休眠,会吃满 CPU——因为 volatile 读不会让线程阻塞或 yield。
- CAS 成功后,JVM 会插入 full barrier(等价于
Unsafe.fullFence()),防止指令重排影响可见性 - 单纯
volatile读(get())比 CAS 快,适合只读场景;但想改值,必须用 CAS 系列方法 - ARM/AArch64 架构下,
volatile语义和 x86 不同,CAS 的屏障行为更关键,不能依赖 volatile 自动“兜底”
替代方案:什么时候不该用 AtomicInteger?
它适合单个整数的高频、低竞争更新。一旦涉及多个字段协同变更(比如余额 + 版本号)、或需要条件复杂判断(“只有当状态为 A 且金额 > 100 才扣款”),AtomicInteger 就力不从心了。
常见误用:AtomicInteger 被当成计数器塞进循环里反复 new,或者在 long 型场景硬用它(得换 AtomicLong)。
- 多字段原子更新 → 用
AtomicReference包装一个不可变对象(如class State { final int balance; final int version; }) - 需要阻塞等待 → 别轮询
get(),改用CountDownLatch或Phaser - 高竞争下 CAS 失败率飙升 → 可考虑
LongAdder(JDK 8+),它用分段累加减少冲突,但sum()不是实时强一致的
真正难的从来不是“怎么用”,而是“用不用得对”。CAS 看似无锁,但失败重试的成本、伪共享(false sharing)导致的缓存行争用、以及和 GC 的交互细节,全藏在看似简单的 incrementAndGet() 调用之下。








