一、Atomic 类的目标
AtomicInteger
、AtomicLong
等的设计目标是:
在无锁(Lock-free) 的情况下,保证单个变量的原子操作。
例如:
java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子 +1,不会有线程竞争问题
二、核心原理:CAS(Compare And Swap)
CAS 是一种原子性 CPU 指令(比如 x86 的 CMPXCHG
),语义是:
"比较并交换":如果当前值 == 期望值,则更新为新值;否则不变。
公式化描述:
java
boolean CAS(V, A, B)
{
if (V == A)
V = B;
else
do nothing;
}
参数解释:
V
:内存中的当前值(实际值)A
:期望值(expected value)B
:要更新的新值(new value)
整个比较 + 赋值操作由 CPU 保证原子性。
三、AtomicInteger 的实现源码分析
java
public class AtomicInteger {
private volatile int value;
//表示 value 字段在对象内存中的偏移量
private static final long valueOffset;
// AtomicInteger类中定义
public final int incrementAndGet() {
// 调用Unsafe类的getAndAddInt方法,
// 参数: (当前对象, value字段的内存偏移量, 增量1)
// 返回值是执行前的旧值,所以最后 +1 得到新值。
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
重点在 unsafe.getAndAddInt()
:
java
// Unsafe.java 中的方法
public final int getAndAddInt(Object obj, long offset, int delta) {
int prev; // 保存当前读取到的旧值
do {
// 从 obj 对象的 offset 位置(也就是 value 字段)读取当前值
// 使用 volatile 语义保证内存可见性
prev = getIntVolatile(obj, offset);
// 调用 CAS(Compare And Swap)原子操作
// 比较:如果内存中该位置的值 == prev,
// 就更新为 prev + delta(这里 delta = 1)
// 否则说明有别的线程改动过,CAS 返回 false,进入下一次自旋重试。
} while (!compareAndSwapInt(obj, offset, prev, prev + delta));
// 当CAS成功(返回true)时,循环退出
// 返回的是修改前的旧值
return prev;
}
👉 整个过程:
- 读取当前值
prev
; - 尝试用 CAS 更新为
prev + delta
; - 如果失败(表示被其他线程改过),则 自旋重试;
- 成功后返回旧值。
这就是 无锁自旋 + CAS 原子更新。
四、为什么需要 volatile
?为什么返回旧值?
1. value
字段被声明为 volatile
:
java
private volatile int value;
作用: 确保每次读取都是主内存最新值,因为 prev = getIntVolatile(obj, offset);
这行代码,每次都是从对象在内存中的偏移量读取最新的旧值。
2. 关于旧值
自我理解:因为不管CAS操作成功与否,CPU指令都会将旧值写到EAX 寄存器中,而寄存器的返回速度是最快的。
五、底层实现依赖:Unsafe / VarHandle
早期(JDK8 及之前):
- 通过
sun.misc.Unsafe.compareAndSwapInt()
实现; - 调用 JVM 本地方法,最终用 CPU 指令
CMPXCHG
。
JDK9 之后:
- 使用更安全的
VarHandle
; - 实现方式仍是 CAS,只是封装 API 改变了。
六、CAS 的优缺点
- 优点:自旋,不会因为第一次没拿到锁,就返回失败,业务还是有成功抢锁的几率的,提高了业务线程的成功率
- 缺点:业务线程会长期占用CPU和内存,如果有高并发,并且如果竞争比较激烈,那么有大量的线程在自旋尝试获取锁,不是快速失败的设计。
- CAS过度竞争后果:
- 内存压力大;
- CPU无效繁忙(CPU利用率虚高,不断重试)->实际任务完成少->系统吞吐量下降;
- 和线程频繁上下文切换导致的CPU繁忙不一样,但结果是一样的->实际任务完成少->系统吞吐量下降;
优点 | 缺点 |
---|---|
无锁,性能高 | ABA 问题 |
不会阻塞 | 自旋长期占用CPU和内存 |
不会引发线程切换 | 只能操作一个变量 |
bash
线程阻塞/切换状态切换:用户态->内核态->用户态
[用户态 T1 执行]
│
▼
发起阻塞 → 系统调用
│
▼
[内核态 T1] 保存寄存器,切换堆栈
│
▼
修改 T1 状态 → Blocked,调度 T2
│
▼
[内核态 T2] 加载寄存器,准备运行
│
▼
切换 T2 到用户态运行
七、典型的扩展场景
类 | 功能 | 底层仍基于 CAS |
---|---|---|
AtomicInteger |
整型原子操作 | ✅ |
AtomicLong |
长整型原子操作 | ✅ |
AtomicReference |
对象引用更新 | ✅ |
AtomicStampedReference |
解决 ABA 问题 | ✅ |
LongAdder |
分段累加优化高并发 | ✅(内部多 CAS) |
八、总结一句话
Atomic
系列类底层确实基于 CAS(Compare-And-Swap)+ volatile + 自旋重试 实现原子性。它是 CPU 原语级别的无锁同步机制,是 Java 并发包最基础的实现之一。