今天我们来深入探讨高并发编程中一个绕不开的核心话题:原子类。无论是接口限流计数器、分布式ID生成器,还是秒杀库存扣减,底层都离不开无锁原子操作。它不仅是性能优化的基石,也是面试中高频出现的考点——CAS底层原理是什么?AtomicInteger源码如何实现?为什么高并发下LongAdder比AtomicLong快这么多?这些问题,我们将在本文中一次讲透,结合真实业务场景与性能对比,让无锁编程不再停留在理论层面。

一、先搞懂:为什么需要原子类?
先看几个最具代表性的应用场景:
- 接口限流计数器——多线程统计QPS,请求数量不能少算;
- 分布式ID生成器——基于原子类自增,保证ID全局唯一;
- 秒杀库存扣减——高并发下库存既不能超卖,也不能少卖。
这些场景的共同特点是什么?多个线程同时修改同一个共享变量,且要求结果绝对准确。而普通的i++,本质上是“读-改-写”三步操作,在多线程环境下根本无法保证原子性:
// 反例:多线程下线程不安全
private int count = 0;
public void increment() {
count++; // 实际执行:int temp = count; count = temp + 1;
}
解决方案其实只有两条路:要么使用互斥锁(synchronized/ReentrantLock),同一时间只允许一个线程操作,但代价是性能大幅下降;要么使用原子类——它基于CPU原生的CAS指令,无锁、无阻塞、轻量高效,才是高并发场景下的首选。
二、CAS核心原理:无锁编程的底层逻辑
原子类之所以能实现“无锁”,依靠的是一个叫做CAS(Compare And Swap)的CPU原子指令。所谓原子指令,就是硬件层面保证该操作不可分割。
1. 三个核心参数
- 内存地址V:要修改的变量,例如AtomicInteger中的value;
- 预期旧值A:线程读取到的最新值;
- 目标新值B:想要改成什么值。
2. 执行逻辑
用伪代码表示如下:
boolean compareAndSwap(V, A, B) {
if (V的当前值 == A) {
V = B; // 符合预期,修改成功
return true;
}
return false; // 被其他线程改过,返回失败
}
流程其实很简单:线程1读取到V=10(A=10),准备改成11(B=11)。此时它判断V当前是否还是10?如果是,就修改;如果已经被其他线程改成了别的值,那就重新读取最新值,再尝试一次——这就是所谓的“自旋”。
3. 关键提醒:CAS并非“完美无缺”
- 优点:无锁,无线程上下文切换,低并发下性能远超锁;
- 缺点:高并发下大量线程同时CAS同一个变量,会导致频繁的自旋重试,CPU占用率可能直线飙升。
三、AtomicInteger源码解析:最常用的原子类
以JDK8的AtomicInteger为例,核心源码其实只需关注两处,其余都是辅助逻辑。
1. 核心结构
public class AtomicInteger extends Number implements Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe(); // JDK底层工具,直接操作内存
private static final long valueOffset; // value的内存偏移地址
private volatile int value; // 实际值,volatile保证可见性
static {
valueOffset = unsafe.objectFieldOffset(
AtomicInteger.class.getDeclaredField("value"));
}
}
这里有两个关键点:Unsafe提供了直接操作内存的能力;valueOffset是value字段的内存偏移量,CAS正是通过这个偏移量直接修改内存中的值。而volatile保证了所有线程都能看到value的最新值。
2. 核心方法:getAndIncrement
public final int getAndIncrement() {
// 自旋CAS:失败则重试,直到成功
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// Unsafe底层实现
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); // 读取最新值
// CAS尝试修改:预期值var5,新值var5+1;失败则循环
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
看到这个do-while循环了吗?这就是自旋——如果CAS失败,就立刻重新读取值、再尝试,直到成功为止。在低并发场景下,一次就能成功,效率极高;但一旦竞争激烈,线程就会在这里空转消耗CPU。
四、CAS的经典问题:ABA问题 & 解决方案
1. 什么是ABA?
想象这样一个场景:线程1读取到V=A,准备改成B;这时线程2插队,先把A改成B,然后又改回A;线程1恢复执行,发现V还是A,认为没人动过,于是修改成功。这就是ABA问题。
风险主要出现在无锁链表或栈这类数据结构中:一个节点被删除后又重新插入(地址或数值相同),CAS判断“没变过”,但实际上链表结构已经改变,可能导致严重的逻辑错误。普通计数业务倒不太怕ABA,真正需要警惕的是那些依赖对象状态一致性的场景。
2. 解决方案:AtomicStampedReference
思路很简单——给变量加一个“版本号”,每次修改版本号加1。CAS操作时,不仅比较值,还要比较版本号:
// 初始化:值为100,版本号为1
AtomicStampedReference ref = new AtomicStampedReference<>(100, 1);
// 正确修改:预期值100,预期版本1,新值150,新版本2
boolean success = ref.compareAndSet(100, 150, 1, 2);
这样一来,即使值被改回了100,版本号已经变成2,CAS仍然会失败,ABA问题就被彻底堵住了。
五、高并发终极优化:LongAdder为什么比AtomicLong快?
1. AtomicLong的缺陷
高并发场景下(比如1000个线程同时计数),所有线程都去CAS竞争同一个value。结果就是大量线程自旋重试,CPU占用率飙到100%,QPS反而急剧下降——典型的“好心办坏事”。
2. LongAdder的核心思想:分段CAS
LongAdder的思路是“空间换时间”。它把原来一个变量拆成多个“分段Cell”,让线程分散到不同的Cell上竞争,最后再把所有Cell的值汇总:
- 无竞争时:直接操作base值,节省内存;
- 有竞争时:修改对应的Cell,减少冲突;
- 冲突严重时:自动扩容Cell数组,进一步分散压力。
3. 核心结构
LongAdder对外只提供业务方法,核心逻辑都复用了父类Striped64:
public class LongAdder extends Striped64 implements Serializable {
public LongAdder() {}
// 核心1:自增1
public void increment() {
add(1L);
}
// 核心2:自增指定值
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
// 先尝试改base;有竞争则改对应Cell
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x))) {
// 调用父类处理竞争、初始化/扩容cells
longAccumulate(x, null, uncontended);
}
}
}
// 核心3:sum()是弱一致性求和,过程中不加锁
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
}
注意sum()方法——它在求和过程中不加锁,高并发下只能拿到近似值。所以如果需要强一致性的计数(比如金融交易),就不要用LongAdder了。
4. AtomicLong vs LongAdder 对比表
(说明:原文此处有对比表格,但输出中无法完整呈现,核心差异概括为:低并发/需要强一致/需要get/set/compareAndSet选AtomicLong;高并发计数/允许轻微误差选LongAdder,性能可提升10倍以上。)
六、实战避坑 & 选型指南
1. 坑1:volatile + 普通运算 ≠ 原子性
// 反例:线程不安全
volatile int count = 0;
count++; // 仍是读-改-写三步,多线程下会错乱
// 正解:用AtomicInteger
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
volatile只保证可见性和禁止指令重排序,不保证原子性。这一点容易混淆。
2. 坑2:原子类只保证单次操作原子
如果业务逻辑需要“先判断再修改”,两次原子方法调用之间仍然可能被打断:
// 反例:线程不安全(两次原子操作之间可能被打断)
if (count.get() < 10) {
count.incrementAndGet();
}
// 正解:加锁或用compareAndSet自旋
while (true) {
int curr = count.get();
if (curr >= 10) break;
if (count.compareAndSet(curr, curr + 1)) break;
}
3. 坑3:高并发下盲目用AtomicLong
秒杀、高QPS统计这类场景,直接用LongAdder,性能差距可能是一个数量级。
七、核心总结
- 原子类基于「CAS + volatile」实现无锁原子操作,低并发下性能远超锁;
- CAS的ABA问题用AtomicStampedReference(版本号)解决;
- 高并发计数选LongAdder(分段CAS),适合统计、监控、QPS、限流等允许轻微误差的场景;
- 低并发、需要强一致、需要get/set/compareAndSet,选AtomicLong,更省内存;
- volatile只保证可见性、禁止重排序,不保证原子性。
