Java原子类与CAS机制:从入门到ABA问题解决方案
一、原子操作底层原理
1. 硬件级支持:CPU原子指令
| 指令 | 作用 | 对应Java实现 |
|---|---|---|
| LOCK CMPXCHG | 比较并交换(32/64位) | Unsafe.compareAndSwapInt |
| LOCK XADD | 原子加法 | AtomicInteger.addAndGet |
| MFENCE | 内存屏障保证可见性 | volatile变量读写 |
2. Java原子类体系结构
graph BT
A[AtomicInteger] --> B[Number]
C[AtomicReference] --> D[Object]
E[AtomicStampedReference] --> C
F[LongAdder] --> G[Striped64]
二、核心原子类源码剖析
1. AtomicInteger实现原理
public class AtomicInteger {
private volatile int value;
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// Unsafe底层调用
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset); // 原子读
} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS自旋
return v;
}
}
2. LongAdder高性能秘密
- Cell数组分散竞争:通过
@Contended避免伪共享 - 最终一致性:
sum()时合并所有Cell值
// Striped64部分源码
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
Cell[] cs; long b, v; int m; Cell c;
if ((cs = cells) != null || !casBase(b = base, b + x)) {
// 竞争激烈时使用Cell数组
if ((c = getProbe()) == 0) {
ThreadLocalRandom.current(); // 初始化探针
wasUncontended = true;
}
if (cs == null || (m = cs.length - 1) < 0)
collide = false;
else if ((c = cs[m & c]) == null)
// 创建新Cell...
}
}
三、ABA问题与解决方案
1. 问题复现场景
AtomicReference<String> ref = new AtomicReference<>("A");
// 线程1:A→B→A
ref.compareAndSet("A", "B");
ref.compareAndSet("B", "A");
// 线程2:检查到值仍是A,但中间状态已变化
ref.compareAndSet("A", "C"); // 成功,但可能不符合业务预期
2. 解决方案对比
| 方案 | 实现原理 | 优缺点 |
|---|---|---|
| 版本号(AtomicStampedReference) | 附加int版本号标记 | 精确但内存开销较大 |
| 时间戳(AtomicMarkableReference) | 使用boolean标记 | 内存友好但精度较低 |
| 业务唯一ID | 使用自增ID代替原始值 | 需要业务改造 |
// AtomicStampedReference示例
AtomicStampedReference<String> ref =
new AtomicStampedReference<>("A", 0);
int[] stamp = new int[1];
String current = ref.get(stamp); // 同时获取值和版本号
ref.compareAndSet(current, "C", stamp[0], stamp[0] + 1);
四、JDK8+增强型原子类
1. Accumulator类使用场景
LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
// 并行累加
IntStream.range(0, 100).parallel().forEach(i -> accumulator.accumulate(i));
System.out.println(accumulator.get()); // 输出4950
2. 性能对比测试
| 操作 | AtomicInteger | LongAdder | LongAccumulator |
|---|---|---|---|
| 100万次单线程累加 | 12ms | 15ms | 18ms |
| 100万次100并发累加 | 420ms | 32ms | 35ms |
五、实战:实现无锁栈
1. Treiber Stack实现
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) { this.item = item; }
}
}
六、常见问题QA
💬 Q1:CAS自旋导致CPU飙升怎么办?
✅ 解决方案:
- 使用
LongAdder替代AtomicLong - 添加短暂Yield:
Thread.yield() - 退化为锁竞争(如
synchronized)
💬 Q2:AtomicInteger和volatile的区别?
✅ 核心差异:
| 维度 | AtomicInteger | volatile |
|---|---|---|
| 原子性 | 支持(CAS) | 不支持(仅可见性) |
| 复合操作 | 提供addAndGet等方法 | 需手动同步 |
| 性能 | 高并发下更好 | 简单场景更高效 |
💬 Q3:什么时候该用字段更新器?
✅ 适用场景:
- 需要原子更新已有类的字段(如第三方库)
- 内存敏感场景(比
AtomicReference省内存)
// 示例:原子更新Person的age字段
AtomicIntegerFieldUpdater<Person> updater =
AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
Person p = new Person();
updater.incrementAndGet(p); // 原子增加age
性能优化建议:
- 高并发计数优先使用
LongAdder - 检查
sun.misc.Unsafe的使用(JDK9+推荐VarHandle) - 避免在热点代码中使用
AtomicStampedReference(内存开销大)
通过
-XX:+PrintAssembly可查看CAS对应的汇编指令