原子性
「一行简单的 counter++,背后藏着三个步骤。」
很多人以为 i++ 是原子操作,一运行才发现数据对不上。
这就是并发编程最常见的坑——原子性。
什么是原子操作
原子操作是不可分割的最小执行单位,执行过程中不会被任何因素打断。
┌─────────────────────────────────────────────┐
│ i++ 不是原子操作 │
├─────────────────────────────────────────────┤
│ │
│ i++ 实际包含三个 CPU 指令: │
│ │
│ 1. LOAD eax, [i] ← 读取 i 的值 │
│ 2. ADD eax, 1 ← 加 1 │
│ 3. STORE [i], eax ← 写回 i │
│ │
│ 在任意两个指令之间,都可能被其他线程打断 │
│ │
└─────────────────────────────────────────────┘多线程环境下会发生「丢失更新」:
线程A: 读取 i=0 ──────► 加1 → i=1
线程B: 读取 i=0 ──────► 加1 → i=1
↑
两个线程都写了1,期望是2哪些是原子操作
| 操作 | 是否原子 | 说明 |
|---|---|---|
x = 1 | ✅ | 基本类型赋值 |
obj.field = v | ⚠️ | 引用赋值本身是原子,但字段不是 |
x++ | ❌ | 读-改-写三步 |
i = i + 10 | ❌ | 复合操作 |
long/double 赋值(JDK 8前) | ⚠️ | 可能非原子 |
非原子性演示
java
public class AtomicityProblemDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
int threadCount = 100;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter++; // 非原子操作!
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("期望结果: " + (100 * 1000));
System.out.println("实际结果: " + counter);
System.out.println("丢失更新: " + (100000 - counter));
}
}运行几次,你会发现实际结果总是小于 100000,而且每次都不一样。
如何保证原子性
方案一:synchronized(最简单)
java
public class SynchronizedAtomicityDemo {
private static int counter = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
int threadCount = 100;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
synchronized (lock) {
counter++;
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("synchronized 结果: " + counter); // 100000,每次都正确
}
}方案二:AtomicInteger(无锁方案)
java
public class AtomicIntegerDemo {
private static final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
int threadCount = 100;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // 原子操作
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("AtomicInteger 结果: " + counter.get()); // 100000
}
}方案对比
| 方案 | 适用场景 | 性能 | 复杂度 |
|---|---|---|---|
| synchronized | 任何操作 | 低(阻塞) | 低 |
| ReentrantLock | 需要公平/可中断 | 中(阻塞) | 中 |
| Atomic* 类 | 单个变量操作 | 高(无锁) | 低 |
| LongAdder | 高并发累加 | 最高(分段) | 低 |
AtomicInteger 常用方法
java
public class AtomicIntegerMethods {
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(10);
// 基本操作
System.out.println("get(): " + ai.get()); // 10
System.out.println("incrementAndGet(): " + ai.incrementAndGet()); // 11
System.out.println("decrementAndGet(): " + ai.decrementAndGet()); // 10
System.out.println("addAndGet(5): " + ai.addAndGet(5)); // 15
// CAS 操作
ai.set(20);
System.out.println("compareAndSet(20, 30): " + ai.compareAndSet(20, 30)); // true
System.out.println("get(): " + ai.get()); // 30
// 更新函数
ai.updateAndGet(x -> x * 2); // 60
System.out.println("updateAndGet(*2): " + ai.get());
// 累加器(更高性能)
LongAdder longAdder = new LongAdder();
longAdder.add(100);
longAdder.increment();
System.out.println("LongAdder: " + longAdder.sum());
}
}复合操作的原子性
AtomicInteger 保证单个操作的原子性,但复合操作(如 check-then-act)仍然需要额外同步:
java
public class CompoundOperationDemo {
// ❌ 非原子复合操作
private static int[] array = {1, 2, 3};
// ✅ 使用 CAS 实现原子复合操作
private static final AtomicReference<int[]> arrayRef =
new AtomicReference<>(new int[]{1, 2, 3});
public static void main(String[] args) {
// 原子更新数组元素
updateArrayElement(0, x -> x * 10);
System.out.println(Arrays.toString(arrayRef.get())); // [10, 2, 3]
}
private static void updateArrayElement(int index, IntUnaryOperator operator) {
while (true) {
int[] current = arrayRef.get();
int updatedValue = operator.applyAsInt(current[index]);
int[] newArray = Arrays.copyOf(current, current.length);
newArray[index] = updatedValue;
// CAS 失败就重试
if (arrayRef.compareAndSet(current, newArray)) {
return;
}
}
}
}LongAdder vs AtomicLong
在超高并发下,LongAdder 比 AtomicLong 性能高得多(分段累加,减少竞争):
java
public class LongAdderPerformanceDemo {
public static void main(String[] args) throws InterruptedException {
int threadCount = 100;
int iterations = 10000;
// AtomicLong:所有线程竞争同一个 value
AtomicLong atomicLong = new AtomicLong(0);
long start = System.nanoTime();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < iterations; j++) {
atomicLong.incrementAndGet();
}
}).start();
}
Thread.sleep(500);
System.out.println("AtomicLong: " + atomicLong.get() + ", 耗时: " +
(System.nanoTime() - start) / 1_000_000 + "ms");
// LongAdder:分段累加,最后求和
LongAdder longAdder = new LongAdder();
start = System.nanoTime();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < iterations; j++) {
longAdder.increment();
}
}).start();
}
Thread.sleep(500);
System.out.println("LongAdder: " + longAdder.sum() + ", 耗时: " +
(System.nanoTime() - start) / 1_000_000 + "ms");
}
}注意:LongAdder.sum() 不是原子操作,应在低并发时调用。
原子性选择口诀
单个变量计数器 → AtomicInteger / AtomicLong
高并发累加 → LongAdder
引用类型 → AtomicReference
复合操作 → synchronized 或 CAS 循环注意事项
- Atomic 不保证复合操作的原子性*:如
counter.incrementAndGet() + 1 - LongAdder.sum() 不是原子:适合最终一致性场景
- ABA 问题:使用
AtomicStampedReference解决 - 不要过度同步:能用原子类解决的,不要用 synchronized
要点回顾
i++不是原子操作,包含「读-改-写」三个步骤- 原子性保证方式:synchronized / ReentrantLock / Atomic* 类
AtomicInteger/AtomicLong适合一般并发场景LongAdder适合高并发累加(性能更高)- 复合操作(check-then-act)仍需额外同步
相关链接
- 可见性 - 原子性之外的另一个维度
- volatile - 保证可见性和有序性
- Happens-Before - 理解可见性的理论基础
