Skip to content

原子性

「一行简单的 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

在超高并发下,LongAdderAtomicLong 性能高得多(分段累加,减少竞争):

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 循环

注意事项

  1. Atomic 不保证复合操作的原子性*:如 counter.incrementAndGet() + 1
  2. LongAdder.sum() 不是原子:适合最终一致性场景
  3. ABA 问题:使用 AtomicStampedReference 解决
  4. 不要过度同步:能用原子类解决的,不要用 synchronized

要点回顾

  • i++ 不是原子操作,包含「读-改-写」三个步骤
  • 原子性保证方式:synchronized / ReentrantLock / Atomic* 类
  • AtomicInteger / AtomicLong 适合一般并发场景
  • LongAdder 适合高并发累加(性能更高)
  • 复合操作(check-then-act)仍需额外同步

相关链接

基于 VitePress 构建