深入浅出 Java 多线程原子类
一、为什么需要原子类?
先看一个经典问题:
java
public class Counter {
private int count = 0;
public void increment() {
count++; // 这行代码不是原子的!
}
}
count++ 看起来是一行,实际上分为三步:
- 读取 count 的值
- 加 1
- 写回 count
多线程同时执行时,可能两个线程都读到 count=5,都加 1 写回 6,结果本应是 7 却变成了 6------这就是竞态条件。
传统解决方案是加锁(synchronized),但锁太重了。原子类提供了无锁的解决方案。
二、原子类的核心原理:CAS
原子类的基石是 CAS(Compare-And-Swap,比较并交换):
CAS(内存地址, 期望值, 新值):
if (内存地址的值 == 期望值) {
内存地址的值 = 新值;
return true;
}
return false;
关键点:这一整步操作由 CPU 指令保证原子性 (如 x86 的 cmpxchg 指令),不需要加锁。
用 CAS 重写 count++:
do {
old = count; // 读取当前值
new = old + 1; // 计算新值
} while (!CAS(count, old, new)); // 如果 count 还是 old,就更新;否则重试
这就是乐观锁思想:假设没有冲突,冲突了就重试。
三、原子类家族
java.util.concurrent.atomic 包下主要有四类:
1. 基本类型原子类
AtomicIntegerAtomicLongAtomicBoolean
2. 引用类型原子类
AtomicReference<V>------ 原子更新对象引用AtomicStampedReference<V>------ 带版本号,解决 ABA 问题AtomicMarkableReference<V>------ 带布尔标记
3. 数组类型原子类
AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray<E>
注意 :原子数组只保证数组元素的原子性,不保证整个数组。
4. 字段更新器(反射方式更新对象字段)
AtomicIntegerFieldUpdater<T>AtomicLongFieldUpdater<T>AtomicReferenceFieldUpdater<T,V>
java
class User {
volatile int age; // 字段必须是 volatile
}
AtomicIntegerFieldUpdater<User> updater =
AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
updater.incrementAndGet(user);
JDK 8 还加入了
LongAdder、DoubleAdder(高并发场景下性能更好,后文详述)。
四、常用 API(以 AtomicInteger 为例)
java
AtomicInteger ai = new AtomicInteger(0);
ai.get(); // 获取当前值
ai.set(10); // 设置值
ai.getAndSet(20); // 获取旧值并设置为新值
ai.incrementAndGet(); // 先加1再返回(++i)
ai.getAndIncrement(); // 先返回再加1(i++)
ai.decrementAndGet(); // --i
ai.getAndDecrement(); // i--
ai.addAndGet(5); // 加5并返回新值
ai.getAndAdd(5); // 返回旧值再加5
ai.compareAndSet(expect, update); // CAS 操作,成功返回 true
ai.weakCompareAndSet(expect, update); // 可能虚假失败,但性能稍好
五、ABA 问题
CAS 有一个经典陷阱:ABA 问题。
场景:
- 线程1 读到值 A,准备改成 C
- 线程2 把 A 改成 B,又改回 A
- 线程1 的 CAS 检查发现还是 A,成功更新为 C
但中间其实已经变过了!某些场景(如栈、链表)这会导致数据错误。
解决方案 :AtomicStampedReference,用"版本号"配合值一起比较。
java
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 0); // 值100,版本0
int stamp = ref.getStamp();
Integer value = ref.getReference();
// 只有值和版本号都匹配才更新
ref.compareAndSet(value, 200, stamp, stamp + 1);
六、性能对比与 LongAdder
普通场景下,原子类性能优于 synchronized。但高并发下,多个线程疯狂 CAS 重试,CPU 会空转。
JDK 8 引入了 LongAdder,思路是分段累加:
LongAdder 内部维护一个 base + Cell[] 数组
- 无竞争时直接 CAS base
- 有竞争时,不同线程分散到不同 Cell 上累加
- 求总和时把 base + 所有 Cell 相加
| 场景 | 推荐 |
|---|---|
| 低并发、需要精确 get 值 | AtomicInteger / AtomicLong |
| 高并发累加统计 | LongAdder |
| 需要 CAS 逻辑 | AtomicXXX |
java
LongAdder adder = new LongAdder();
adder.increment(); // 各线程分散累加
adder.sum(); // 汇总(非原子快照)
七、实战示例
场景1:线程安全计数器
java
AtomicInteger counter = new AtomicInteger();
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.submit(counter::incrementAndGet);
}
pool.shutdown();
// counter.get() == 1000
场景2:无锁单例(CAS 实现)
java
class Singleton {
private static final AtomicReference<Singleton> INSTANCE =
new AtomicReference<>();
public static Singleton getInstance() {
for (;;) {
Singleton inst = INSTANCE.get();
if (inst != null) return inst;
inst = new Singleton();
if (INSTANCE.compareAndSet(null, inst)) return inst;
}
}
}
场景3:状态机的原子切换
java
AtomicReference<State> state = new AtomicReference<>(State.INIT);
// 只有当前是 INIT 才转 RUNNING
state.compareAndSet(State.INIT, State.RUNNING);
八、注意事项
- 原子类只保证单个变量的原子性。多个变量组合操作仍需加锁。
- 复合操作不原子 :
if (ai.get() > 0) ai.decrementAndGet();不是原子的,要配合 CAS 循环。 - ABA 问题在链式结构、栈操作中要小心。
- 原子引用不保证对象内部字段的原子性,只是引用的赋值原子。
- 字段更新器的字段必须 volatile,且不能是 private(反射访问需要)。
- 高并发累加 优先考虑
LongAdder。
九、总结一句话
原子类 = volatile 保证可见性 + CAS 保证原子性 + 乐观重试代替阻塞加锁,是 Java 并发编程中最轻量的无锁利器。
理解了 CAS,就理解了原子类的灵魂;理解了 ABA 和 LongAdder,就摸到了它的边界和升级方向。