原子类详解
飞翔科技性能优化作战室,凌晨两点。
小崔(初级开发,盯着火焰图):"奇怪,我用
synchronized保护的那个全局请求计数器,CPU 占用怎么会这么高?每秒才 5 万 QPS 啊。"白歌(首席架构师):"synchronized 在竞争激烈的时候会膨胀成重量级锁,频繁的上下文切换把你的 CPU 吃光了。JDK 8 的原子类用 CAS 无锁算法,性能能提升几十倍。"
Frank(后端专家,在白板上画 CPU 缓存图):"CAS — Compare-And-Swap — is a single atomic CPU instruction. No locks, no context switches. The CPU executes
cmpxchgand you get atomicity at the hardware level."大翔(CTO):"但是 CAS 也有 ABA 问题。朱璐上次压测就踩过这个坑——引用被改来改去,看起来值没变,实际对象早换了。"
朱璐(测试组长,委屈):"我当时就说了用
AtomicStampedReference加个版本号就好了嘛..."白歌:"那今晚我们就从 CAS 原理讲到 LongAdder,再到 ABA 问题的解决方案,把原子类彻底吃透。"
一、定义表
| 概念 | 定义 | JDK 8 关键类 |
|---|---|---|
| CAS | Compare-And-Swap:比较并交换,一条 CPU 原子指令,比较内存值与期望值,相等则更新 | sun.misc.Unsafe |
| AtomicInteger | 基于 CAS 的原子 int,提供 incrementAndGet 等无锁操作 | java.util.concurrent.atomic |
| AtomicLong | 基于 CAS 的原子 long(JDK 8 中 VM 层面优化了 64 位 CAS) | java.util.concurrent.atomic |
| AtomicBoolean | 基于 CAS 的原子 boolean,内部用 int 实现 | java.util.concurrent.atomic |
| AtomicReference | 基于 CAS 的原子对象引用 | java.util.concurrent.atomic |
| LongAdder | JDK 8 新增:分段累加器,高并发下性能远优于 AtomicLong | java.util.concurrent.atomic |
| Striped64 | LongAdder/DoubleAdder 的基类,实现分段 CAS(Cell 数组) | java.util.concurrent.atomic |
| ABA 问题 | CAS 操作中值从 A→B→A,线程误以为值未变 | — |
| AtomicStampedReference | 带版本号(stamp)的原子引用,解决 ABA 问题 | java.util.concurrent.atomic |
| AtomicMarkableReference | 带布尔标记的原子引用,适用于"是否被修改过"场景 | java.util.concurrent.atomic |
| AtomicIntegerFieldUpdater | 基于反射的原子字段更新器,用于对已有类的 volatile int 字段进行 CAS | java.util.concurrent.atomic |
二、CAS 原理深度解析
2.1 CAS 循环流程图
2.2 CAS 的 CPU 层面实现
// Unsafe 类中的 CAS 方法(native)
public final native boolean compareAndSwapInt(
Object o, // 目标对象
long offset, // 字段内存偏移量
int expected, // 期望值
int x // 新值
);
// 对应 x86 CPU 指令: LOCK CMPXCHG
三、ABA 问题图解
四、LongAdder 分段累加原理(JDK 8 核心优化)
核心思想:Striped64 将热点分散到多个 Cell,每个线程 CAS 自己的 Cell,竞争降到 1/N。sum() 时累加所有 Cell,保证最终一致性(非强一致)。
五、代码示例
示例一:飞翔科技全局请求计数器——AtomicInteger vs synchronized 性能对比
场景:飞翔科技的 API 网关需要维护一个全局请求计数器,每秒数万次递增操作。对比 synchronized、AtomicInteger 和 LongAdder 三种方案的性能。
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* 飞翔科技 —— 全局请求计数器性能对比
* synchronized vs AtomicInteger vs LongAdder
*/
public class CounterPerformanceComparison {
// 方案1:synchronized
static class SyncCounter {
private long count = 0;
public synchronized void increment() { count++; }
public synchronized long get() { return count; }
}
// 方案2:AtomicInteger
static class AtomicCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
public int get() { return count.get(); }
}
// 方案3:LongAdder(JDK 8)
static class AdderCounter {
private final LongAdder count = new LongAdder();
public void increment() { count.increment(); }
public long get() { return count.sum(); }
}
/**
* 执行压测
*/
static long benchmark(Runnable task, int threadCount, int iterationsPerThread)
throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executor.execute(() -> {
try {
startLatch.await(); // 等待发令
for (int j = 0; j < iterationsPerThread; j++) {
task.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
doneLatch.countDown();
}
});
}
long start = System.nanoTime();
startLatch.countDown(); // 发令!
doneLatch.await();
long elapsed = System.nanoTime() - start;
executor.shutdown();
return elapsed;
}
public static void main(String[] args) throws Exception {
final int THREADS = 10;
final int ITERATIONS = 1_000_000; // 每线程100万次
System.out.println("===== 飞翔科技计数器性能对比测试 =====");
System.out.printf("线程数: %d | 每线程操作数: %,d | 总操作数: %,d%n%n",
THREADS, ITERATIONS, THREADS * ITERATIONS);
// 预热 JIT
SyncCounter warmUp = new SyncCounter();
benchmark(warmUp::increment, 2, 1000);
// 测试 synchronized
SyncCounter syncCounter = new SyncCounter();
long syncTime = benchmark(syncCounter::increment, THREADS, ITERATIONS);
System.out.printf("synchronized: %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
syncCounter.get(),
syncTime / 1_000_000,
(THREADS * ITERATIONS) * 1_000_000_000.0 / syncTime);
// 测试 AtomicInteger
AtomicCounter atomicCounter = new AtomicCounter();
long atomicTime = benchmark(atomicCounter::increment, THREADS, ITERATIONS);
System.out.printf("AtomicInteger: %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
atomicCounter.get(),
atomicTime / 1_000_000,
(THREADS * ITERATIONS) * 1_000_000_000.0 / atomicTime);
// 测试 LongAdder
AdderCounter adderCounter = new AdderCounter();
long adderTime = benchmark(adderCounter::increment, THREADS, ITERATIONS);
System.out.printf("LongAdder: %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
adderCounter.get(),
adderTime / 1_000_000,
(THREADS * ITERATIONS) * 1_000_000_000.0 / adderTime);
// 性能对比
System.out.println("\n===== 性能倍数(以 synchronized 为基准) =====");
System.out.printf("AtomicInteger 是 synchronized 的 %.1f 倍%n",
(double) syncTime / atomicTime);
System.out.printf("LongAdder 是 synchronized 的 %.1f 倍%n",
(double) syncTime / adderTime);
System.out.printf("LongAdder 是 AtomicInteger 的 %.1f 倍%n",
(double) atomicTime / adderTime);
}
}
控制台输出:
===== 飞翔科技计数器性能对比测试 =====
线程数: 10 | 每线程操作数: 1,000,000 | 总操作数: 10,000,000
synchronized: 10,000,000 ops | 耗时: 1,847 ms | TPS: 5,414,185
AtomicInteger: 10,000,000 ops | 耗时: 512 ms | TPS: 19,531,250
LongAdder: 10,000,000 ops | 耗时: 98 ms | TPS: 102,040,816
===== 性能倍数(以 synchronized 为基准) =====
AtomicInteger 是 synchronized 的 3.6 倍
LongAdder 是 synchronized 的 18.8 倍
LongAdder 是 AtomicInteger 的 5.2 倍
示例二:飞翔科技无锁栈——AtomicReference 实现线程安全的链表
场景:飞翔科技的实时日志系统使用无锁栈(Treiber Stack)来缓存待处理的日志条目,多个采集线程并发 push/pop,用 AtomicReference 实现无锁操作。
import java.util.concurrent.atomic.AtomicReference;
/**
* 飞翔科技 —— 无锁日志栈(Treiber Stack)
* 使用 AtomicReference 实现线程安全的单向链表栈
*/
public class LockFreeLogStack {
// 日志节点
static class LogNode {
final String logEntry;
final long timestamp;
LogNode next;
LogNode(String logEntry) {
this.logEntry = logEntry;
this.timestamp = System.currentTimeMillis();
}
@Override
public String toString() {
return String.format("[%tT] %s", timestamp, logEntry);
}
}
// 无锁栈
static class TreiberStack {
// 栈顶指针 —— AtomicReference 保证原子性
private final AtomicReference<LogNode> top =
new AtomicReference<>(null);
/**
* 入栈(CAS 循环)
*/
public void push(String logEntry) {
LogNode newNode = new LogNode(logEntry);
LogNode oldTop;
do {
oldTop = top.get(); // 读取当前栈顶
newNode.next = oldTop; // 新节点指向旧栈顶
// CAS: 期望栈顶仍是 oldTop,则更新为 newNode
} while (!top.compareAndSet(oldTop, newNode));
}
/**
* 出栈(CAS 循环)
*/
public LogNode pop() {
LogNode oldTop;
LogNode newTop;
do {
oldTop = top.get();
if (oldTop == null) {
return null; // 栈空
}
newTop = oldTop.next; // 新栈顶 = 旧栈顶的 next
} while (!top.compareAndSet(oldTop, newTop));
return oldTop;
}
/**
* 查看栈顶(不弹出)
*/
public LogNode peek() {
return top.get();
}
public int size() {
int count = 0;
LogNode node = top.get();
while (node != null) {
count++;
node = node.next;
}
return count;
}
}
public static void main(String[] args) throws InterruptedException {
TreiberStack logStack = new TreiberStack();
System.out.println("===== 飞翔科技实时日志收集系统 =====");
System.out.println("使用无锁栈 (Treiber Stack) 缓存日志条目\n");
// 模拟 5 个采集线程并发 push 日志
Thread[] producers = new Thread[5];
for (int t = 0; t < 5; t++) {
final int threadId = t;
producers[t] = new Thread(() -> {
for (int i = 0; i < 20; i++) {
String log = String.format("采集器-%d | 日志条目 #%d", threadId, i);
logStack.push(log);
// 模拟采集间隔
try {
Thread.sleep((long)(Math.random() * 5));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "collector-" + t);
producers[t].start();
}
// 等待所有采集完成
for (Thread t : producers) {
t.join();
}
System.out.printf("日志栈中总条目数: %d%n", logStack.size());
// 消费日志(pop 所有条目)
System.out.println("\n===== 弹出所有日志(最新优先) =====");
int count = 0;
LogNode node;
while ((node = logStack.pop()) != null) {
System.out.printf("[%d] %s%n", ++count, node);
}
// 验证栈已空
System.out.printf("%n栈已清空,剩余: %d 条%n", logStack.size());
System.out.println("无锁栈操作完成!");
}
}
控制台输出(部分):
===== 飞翔科技实时日志收集系统 =====
使用无锁栈 (Treiber Stack) 缓存日志条目
日志栈中总条目数: 100
===== 弹出所有日志(最新优先) =====
[1] [14:32:05] 采集器-4 | 日志条目 #19
[2] [14:32:05] 采集器-3 | 日志条目 #19
[3] [14:32:05] 采集器-2 | 日志条目 #19
...
[99] [14:32:00] 采集器-0 | 日志条目 #1
[100] [14:32:00] 采集器-1 | 日志条目 #0
栈已清空,剩余: 0 条
无锁栈操作完成!
六、AtomicStampedReference 解决 ABA 问题
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 飞翔科技 —— AtomicStampedReference 解决 ABA 问题
*
* 场景:链表节点回收复用,用版本号(stamp)标识是否被修改过
*/
public class ABAProblemSolution {
// 使用带版本号的原子引用
static class SafeStack<T> {
static class Node<T> {
final T value;
Node<T> next;
Node(T value) { this.value = value; }
}
// AtomicStampedReference: 引用 + 版本号
private final AtomicStampedReference<Node<T>> top =
new AtomicStampedReference<>(null, 0);
public void push(T value) {
Node<T> newNode = new Node<>(value);
int[] stampHolder = new int[1];
Node<T> oldTop;
do {
oldTop = top.get(stampHolder); // 获取当前栈顶和版本号
int oldStamp = stampHolder[0];
newNode.next = oldTop;
// CAS: 比较引用和版本号,都匹配才更新,版本号+1
if (top.compareAndSet(oldTop, newNode, oldStamp, oldStamp + 1)) {
return; // 成功
}
// 失败则重试
} while (true);
}
public T pop() {
int[] stampHolder = new int[1];
Node<T> oldTop;
Node<T> newTop;
do {
oldTop = top.get(stampHolder);
if (oldTop == null) return null;
int oldStamp = stampHolder[0];
newTop = oldTop.next;
if (top.compareAndSet(oldTop, newTop, oldStamp, oldStamp + 1)) {
return oldTop.value;
}
} while (true);
}
}
public static void main(String[] args) throws InterruptedException {
SafeStack<String> safeStack = new SafeStack<>();
System.out.println("===== ABA 问题复现与解决 =====");
// 线程1: 执行慢操作
Thread t1 = new Thread(() -> {
safeStack.push("A");
safeStack.push("B");
safeStack.push("C");
System.out.println("[线程1] 压入: C, B, A");
// 准备 pop "C"(但中间会被打断)
System.out.println("[线程1] 开始 pop C...");
// 使用 AtomicStampedReference 后,即使被ABA干扰也能检测到
String val = safeStack.pop();
System.out.println("[线程1] pop 结果: " + val +
" (版本号机制保证正确性)");
}, "Thread-1");
Thread t2 = new Thread(() -> {
try { Thread.sleep(10); } catch (InterruptedException e) {}
// 干扰线程:快速 pop 再 push
String v1 = safeStack.pop(); // pop C
String v2 = safeStack.pop(); // pop B
System.out.println("[线程2] 干扰操作: pop了 " + v1 + " 和 " + v2);
safeStack.push("X");
System.out.println("[线程2] push X (制造ABA场景)");
}, "Thread-2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("\n结论: AtomicStampedReference 通过版本号避免了 ABA 问题。");
}
}
七、易错场景
7.1 误用 AtomicInteger 做复合操作
// ❌ 错误:get() + compareAndSet() 不是原子操作!
AtomicInteger balance = new AtomicInteger(100);
// 线程A: 检查余额
int current = balance.get(); // 读到 100
// 线程B 此时扣款 80 → balance = 20
if (current >= 80) {
balance.compareAndSet(current, current - 80); // CAS 失败!
// 正确做法:直接用 getAndAdd / accumulateAndGet
}
正确做法:使用 balance.getAndUpdate(x -> x >= 80 ? x - 80 : x) 或 updateAndGet()(JDK 8)。
7.2 LongAdder 的 sum() 不是强一致的
// ❌ 错误认知:以为 sum() 返回的是精确的瞬时值
LongAdder adder = new LongAdder();
// 10个线程并发 increment
// 调用 sum() 时,可能有 Cell 中的值在变化
long val = adder.sum(); // sum() 不是强一致的!返回近似值
正确理解:LongAdder.sum() 遍历所有 Cell 累加,遍历期间其他线程可能修改 Cell,所以返回的是"近似值"。如果需要精确值,请在并发操作停止后调用,或使用 sumThenReset()。
7.3 忘记 AtomicReference 比较的是引用而非内容
// ❌ 陷阱:AtomicReference 用 == 比较,不是 equals()!
AtomicReference<String> ref = new AtomicReference<>(new String("hello"));
boolean result = ref.compareAndSet(new String("hello"), "world");
System.out.println(result); // false!因为是不同对象
正确理解:compareAndSet 使用 == 比较引用,不是 equals()。对于值类型(String、Integer 等),务必持有同一个对象引用。
7.4 AtomicIntegerFieldUpdater 字段必须为 volatile
// ❌ 错误:字段不是 volatile 会抛异常
class Counter {
int count = 0; // 缺少 volatile!
}
AtomicIntegerFieldUpdater<Counter> updater =
AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
// 运行时: java.lang.IllegalArgumentException: Must be volatile type
正确做法:目标字段必须声明为 volatile int,且不能是 private(除非调用者有访问权限)。
八、面试考点
Q1:CAS 的 ABA 问题是什么?如何解决?(高频)
答:
- ABA 问题:CAS 比较的只是值,如果值经历了 A→B→A 的变化,CAS 会误判为"值未改变",从而执行交换。在引用类型场景下(如链表节点),节点可能已被删除回收,造成悬空指针。
- 解决方案:
AtomicStampedReference<V>:维护一个 int 版本号(stamp),每次更新 stamp+1,同时比较引用和版本号。AtomicMarkableReference<V>:维护一个 boolean 标记,适合"是否被修改过"的简单场景。- 关键:JDK 8 中
compareAndSet(V expectedRef, V newRef, int expectedStamp, int newStamp)是原子操作。
Q2:AtomicLong 和 LongAdder 的区别?什么场景用哪个?(高频)
答:
| AtomicLong | LongAdder (JDK 8) |
|---|---|
| 单值 CAS,高并发时大量自旋 | 分段 Cell 数组,分散热点 |
incrementAndGet() 立即返回精确值 | increment() 不返回值,sum() 返回近似值 |
| 低~中并发下性能好 | 高并发下性能远超 AtomicLong |
| 内存占用小 | 内存占用大(Cell 数组) |
| 需要精确值的场景(如序列号生成) | 统计计数场景(如 QPS 统计) |
核心原理:LongAdder 继承 Striped64,将竞争分散到多个 Cell(通过 Thread.probe 哈希映射),需要 sum 时累加 base + 所有 Cell。
Q3:AtomicReference 的 compareAndSet 用的是 == 还是 equals?
答:compareAndSet 底层调用 Unsafe.compareAndSwapObject,使用的是 引用相等(==),而非 equals()。这是因为 CAS 在 CPU 层面比较的是内存地址。如果需要基于 equals() 的原子更新,需要自行配合 compareAndSet 循环实现。
Q4:JDK 8 中 AtomicInteger 新增了哪些方法?
答:JDK 8 为所有原子类新增了函数式更新方法:
getAndUpdate(IntUnaryOperator)— 原子地更新并返回旧值。updateAndGet(IntUnaryOperator)— 原子地更新并返回新值。getAndAccumulate(int x, IntBinaryOperator)— 原子地累加并返回旧值。accumulateAndGet(int x, IntBinaryOperator)— 原子地累加并返回新值。
AtomicInteger ai = new AtomicInteger(5);
ai.updateAndGet(x -> x * 3); // 15
ai.getAndAccumulate(10, (a, b) -> a + b); // 返回15,然后变成25
这些方法使原子类可以配合 Lambda 表达式使用,极大简化了复合 CAS 操作。
九、原子类选型速查表
| 场景 | 推荐 |
|---|---|
| 计数器,低竞争 | AtomicInteger / AtomicLong |
| 计数器,高竞争(QPS统计) | LongAdder |
| 布尔标志位 | AtomicBoolean |
| 对象引用更新 | AtomicReference<V> |
| 需要解决 ABA 问题 | AtomicStampedReference<V> |
| 只需标记"是否被修改" | AtomicMarkableReference<V> |
| 对已有类的 volatile 字段做 CAS | AtomicIntegerFieldUpdater<T> |
| 数组元素原子操作 | AtomicIntegerArray / AtomicLongArray |
| 复合 CAS 操作 | updateAndGet / accumulateAndGet(JDK 8 Lambda) |
上一篇:并发工具类详解 | 下一篇:ThreadLocal详解