【Java线程安全实战】⑤ 原子类(Atomic)深度解析:无锁编程(Lock-Free)的终极奥义(增强版)

📖目录

  • 引子:一个翻车的计数器,揭开了并发世界的"无锁"秘密
  • [1. 无锁编程:从"超市排队"到"自助结账"的思维跃迁](#1. 无锁编程:从“超市排队”到“自助结账”的思维跃迁)
    • [1.1 悲观锁 vs 乐观锁:生活中的比喻](#1.1 悲观锁 vs 乐观锁:生活中的比喻)
    • [1.2 CAS 是什么?CPU 的"原子承诺"](#1.2 CAS 是什么?CPU 的“原子承诺”)
  • [2. Java 原子类全家桶:从 JDK 5 到 JDK 21 的演进](#2. Java 原子类全家桶:从 JDK 5 到 JDK 21 的演进)
  • [3. 原子类有哪些?分类与核心实现一览](#3. 原子类有哪些?分类与核心实现一览)
    • [3.1 类图](#3.1 类图)
  • [4. 源码深潜:`AtomicInteger.getAndIncrement()` 的无锁精髓](#4. 源码深潜:AtomicInteger.getAndIncrement() 的无锁精髓)
    • [4.1 JDK 8:基于 `sun.misc.Unsafe`](#4.1 JDK 8:基于 sun.misc.Unsafe)
    • [4.2 JDK 17+:基于 `VarHandle`](#4.2 JDK 17+:基于 VarHandle)
  • [5. 为什么 CPU 要提供原子操作?Java 是如何调用它的?](#5. 为什么 CPU 要提供原子操作?Java 是如何调用它的?)
    • [5.1 多核时代的硬件刚需](#5.1 多核时代的硬件刚需)
    • [5.2 硬件如何保证原子性?](#5.2 硬件如何保证原子性?)
    • [5.3 Java 到 CPU 的调用链](#5.3 Java 到 CPU 的调用链)
  • [6. 可执行示例代码](#6. 可执行示例代码)
    • [示例 1:`AtomicInteger` vs `synchronized` 计数器](#示例 1:AtomicInteger vs synchronized 计数器)
    • [示例 2:高并发场景用 `LongAdder` 更快](#示例 2:高并发场景用 LongAdder 更快)
  • [7. 经典陷阱:ABA 问题与解决方案](#7. 经典陷阱:ABA 问题与解决方案)
    • [7.1 什么是 ABA 问题?](#7.1 什么是 ABA 问题?)
    • [7.2 解决方案:加"版本号"或"标记位"](#7.2 解决方案:加“版本号”或“标记位”)
    • [7.3 示例:用 `AtomicStampedReference` 避免 ABA](#7.3 示例:用 AtomicStampedReference 避免 ABA)
  • [8. 何时使用原子类?性能对比与选型指南](#8. 何时使用原子类?性能对比与选型指南)
    • [8.1 优势 vs 劣势](#8.1 优势 vs 劣势)
    • [8.2 性能实验(简化版)](#8.2 性能实验(简化版))
      • [8.2.1 示例:`AtomicInteger` vs `synchronized` 计数器性能对比](#8.2.1 示例:AtomicInteger vs synchronized 计数器性能对比)
      • [8.2.2 典型运行结果(JDK 17, 8 核 CPU):](#8.2.2 典型运行结果(JDK 17, 8 核 CPU):)
      • [8.2.3 示例:`LongAdder` vs `AtomicLong` 高并发计数性能对比](#8.2.3 示例:LongAdder vs AtomicLong 高并发计数性能对比)
      • [8.2.4 典型运行结果(JDK 17, 8 核 CPU, 300 线程):](#8.2.4 典型运行结果(JDK 17, 8 核 CPU, 300 线程):)
    • [8.3 选型建议](#8.3 选型建议)
  • [9. 哪些知名数据结构用了原子类?](#9. 哪些知名数据结构用了原子类?)
  • [10. 下一篇预告:你还没见过的并发神器](#10. 下一篇预告:你还没见过的并发神器)
  • [11. 延伸阅读:经典书籍推荐](#11. 延伸阅读:经典书籍推荐)
  • [12. 参考链接](#12. 参考链接)
  • [13. 总结](#13. 总结)

引子:一个翻车的计数器,揭开了并发世界的"无锁"秘密

想象你和你的室友一起合租了一台智能洗衣机。这台洗衣机有个"使用次数计数器",每次用完就 +1。你们俩都很守规矩,但问题来了------如果你们同时按下"结束使用"按钮,计数器会少算一次吗?

在 Java 里,如果你写的是:

java 复制代码
public class UnsafeCounter {
    private int count = 0;
    public void increment() {
        count++; // 非原子操作!
    }
}

那么答案是:大概率会少算

为什么?因为 count++ 实际上是三步操作:

  1. 读取 count 的值;
  2. 加 1;
  3. 写回新值。

如果两个线程几乎同时执行这三步,就会出现"覆盖写入"------就像两个人同时在超市自助结账机上扫码,结果只扣了一次钱。

这时候,你可能会想到加锁:

java 复制代码
synchronized void increment() { count++; }

没错,这能解决问题。但锁有代价:线程要排队、上下文切换、可能死锁......有没有一种方式,不排队也能保证正确性

答案就是:原子类(Atomic Classes) ------ 它们用的是 CPU 硬件级别的"无锁魔法":CAS(Compare-And-Swap)


1. 无锁编程:从"超市排队"到"自助结账"的思维跃迁

1.1 悲观锁 vs 乐观锁:生活中的比喻

  • 悲观锁(如 synchronized

    就像超市收银台------你必须排队,一个人结账时,其他人干等着。安全,但效率低

  • 乐观锁(如 AtomicInteger

    就像自助结账机------你扫码、付款,系统最后检查"商品价格没变吧?库存够吧?"

    如果一切 OK,交易成功;否则,重试一次
    高效,但可能要多试几次

核心思想先干,再验;不对就重来


1.2 CAS 是什么?CPU 的"原子承诺"

CAS 是一条 CPU 指令,形如:

复制代码
CAS(address, expectedValue, newValue)

含义:如果 address 处的值等于 expectedValue,就把它改成 newValue,并返回 true;否则返回 false

这个操作是硬件保证的原子性------不会被中断,也不会被其他线程干扰。

💡 关键点 :CAS 不需要操作系统介入,不涉及线程阻塞,因此叫 Lock-Free(无锁)


2. Java 原子类全家桶:从 JDK 5 到 JDK 21 的演进

原子类并非 Java 一开始就有的。它的诞生,是为了解决高并发下轻量级同步的需求

JDK 版本 重大事件 原子类相关进展
JDK 1.0 ~ 1.4 无原子类 只能靠 synchronizedvolatile(后者不能保证复合操作原子性)
JDK 5 (2004) JSR-166 引入 java.util.concurrent 首次引入 AtomicInteger, AtomicReference ,基于 sun.misc.Unsafe
JDK 6/7 优化与普及 广泛用于 ConcurrentHashMap、线程池等
JDK 8 (2014) 引入 LongAdder / DoubleAdder 为高并发计数场景优化 ,解决 AtomicLong 在激烈竞争下的性能瓶颈
JDK 9+ 模块化 & 安全加固 Unsafe 被限制访问,引入 VarHandle 作为官方替代
JDK 17/21 (LTS) 稳定演进 VarHandle 成为主流,Atomic 类内部逐步迁移

📌 设计目的

  • 提供比锁更轻量的线程安全机制;
  • 支持单变量的原子更新;
  • 为高性能并发框架(如 Netty、Disruptor)提供基础构件。

3. 原子类有哪些?分类与核心实现一览

3.1 类图

AtomicInteger
AtomicLong
AtomicBoolean
AtomicReference<T>
AtomicMarkableReference<T>
AtomicStampedReference<T>
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray<T>
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater<T,V>
<<abstract>>
Striped64
LongAdder
DoubleAdder
LongAccumulator
DoubleAccumulator
Object

💡 你可以将上述代码粘贴到支持 Mermaid 的 Markdown 编辑器(如 Typora、VS Code 插件、GitLab 等)中直接渲染。


4. 源码深潜:AtomicInteger.getAndIncrement() 的无锁精髓

我们以 JDK 8JDK 17+ 两个版本对比分析。

4.1 JDK 8:基于 sun.misc.Unsafe

java 复制代码
// AtomicInteger.java (JDK 8)
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

Unsafe.getAndAddInt真实实现(HotSpot JVM 内联)逻辑如下(伪代码):

java 复制代码
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);          // volatile 读
    } while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS
    return v;
}
  • getIntVolatile:保证读取的是主内存最新值;
  • compareAndSwapInt:调用 CPU 的 cmpxchg 指令(x86)或 LDREX/STREX(ARM);
  • 整个过程无锁、无阻塞、由硬件保证原子性

4.2 JDK 17+:基于 VarHandle

java 复制代码
// AtomicInteger.java (JDK 17+ 简化版)
private static final VarHandle VALUE;

static {
    try {
        MethodHandles.Lookup l = MethodHandles.privateLookupIn(AtomicInteger.class, MethodHandles.lookup());
        VALUE = l.findVarHandle(AtomicInteger.class, "value", int.class);
    } catch (Exception e) {
        throw new Error(e);
    }
}

public final int getAndIncrement() {
    return (int) VALUE.getAndAdd(this, 1);
}

VarHandle.getAndAdd 最终也会编译为相同的 CAS 指令,但绕过了 Unsafe 的反射限制,符合模块化安全要求。

🔍 关键演进

  • JDK 5~8:依赖内部 API Unsafe
  • JDK 9+:官方推荐 VarHandle
  • OpenJDK 源码中,AtomicInteger 在 JDK 17 后已完全迁移到 VarHandle

5. 为什么 CPU 要提供原子操作?Java 是如何调用它的?

前面我们看到 AtomicInteger 通过 CAS 实现无锁并发。但你有没有想过:CAS 从哪来?Java 怎么让 CPU 执行它?

5.1 多核时代的硬件刚需

在单核 CPU 时代,操作系统可通过"关中断"保证一段代码不被抢占。

多核 CPU 同时访问同一内存地址时,软件无法协调 ------必须由硬件提供原子指令

现代 CPU(x86/ARM/RISC-V)都内置了 Compare-And-Swap(CAS)类指令

  • x86:LOCK CMPXCHG
  • ARM:LDREX / STREX

这些指令的语义是:"仅当内存值等于预期值时,才更新为新值",且整个过程不可分割。


5.2 硬件如何保证原子性?

CPU 并非"锁住整个内存",而是利用 缓存一致性协议(如 MESI)

  • 执行 LOCK 指令时,仅锁定一个缓存行(Cache Line,通常 64 字节)
  • 其他核心对该地址的缓存自动失效;
  • 操作完成后,新值通过缓存协议同步。

这使得原子操作既安全又高效,远优于早期"总线锁"。


5.3 Java 到 CPU 的调用链

Java 无法直接写汇编,但 JVM 为其搭好了桥:

plaintext 复制代码
AtomicInteger.getAndIncrement()
  ↓
Unsafe.getAndAddInt()          ← JDK 8
VarHandle.getAndAdd()          ← JDK 9+
  ↓
JVM 内部 C++ 运行时(HotSpot)
  ↓
内联为机器指令:lock cmpxchg(x86)

关键点:

  • UnsafeVarHandle 是 JVM 提供的本地方法接口
  • JIT 编译器会将 CAS 调用直接编译为一条带 LOCK 前缀的汇编指令
  • 整个过程无系统调用、无线程阻塞,完全在用户态完成。

✅ 所以,"无锁"不是没有锁,而是把锁的粒度缩小到 CPU 缓存行级别,由硬件高效处理。


6. 可执行示例代码

示例 1:AtomicInteger vs synchronized 计数器

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    private static int badCounter = 0;
    private static final AtomicInteger goodCounter = new AtomicInteger(0);

    public static synchronized void badIncrement() {
        badCounter++;
    }

    public static void main(String[] args) throws InterruptedException {
        int threads = 100;
        int increments = 10_000;

        Thread[] t1 = new Thread[threads];
        Thread[] t2 = new Thread[threads];

        // 测试非原子计数器
        for (int i = 0; i < threads; i++) {
            t1[i] = new Thread(() -> {
                for (int j = 0; j < increments; j++) {
                    badIncrement();
                }
            });
        }

        // 测试原子计数器
        for (int i = 0; i < threads; i++) {
            t2[i] = new Thread(() -> {
                for (int j = 0; j < increments; j++) {
                    goodCounter.incrementAndGet();
                }
            });
        }

        long start = System.currentTimeMillis();
        for (Thread t : t1) t.start();
        for (Thread t : t1) t.join();
        long time1 = System.currentTimeMillis() - start;

        start = System.currentTimeMillis();
        for (Thread t : t2) t.start();
        for (Thread t : t2) t.join();
        long time2 = System.currentTimeMillis() - start;

        System.out.println("Expected: " + (threads * increments));
        System.out.println("Bad counter: " + badCounter + " (loss: " + (threads * increments - badCounter) + ")");
        System.out.println("Good counter: " + goodCounter.get());
        System.out.println("Synchronized time: " + time1 + "ms");
        System.out.println("Atomic time: " + time2 + "ms");
    }
}

执行结果典型输出

复制代码
Expected: 1000000
Bad counter: 1000000 (loss: 0)
Good counter: 1000000
Synchronized time: 63ms
Atomic time: 26ms

示例 2:高并发场景用 LongAdder 更快

java 复制代码
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class AdderVsAtomic {
    public static void main(String[] args) throws InterruptedException {
        int threads = 200;
        int increments = 100_000;

        AtomicLong atomic = new AtomicLong();
        LongAdder adder = new LongAdder();

        Thread[] t1 = new Thread[threads];
        Thread[] t2 = new Thread[threads];

        for (int i = 0; i < threads; i++) {
            t1[i] = new Thread(() -> {
                for (int j = 0; j < increments; j++) {
                    atomic.incrementAndGet();
                }
            });
            t2[i] = new Thread(() -> {
                for (int j = 0; j < increments; j++) {
                    adder.increment();
                }
            });
        }

        long start = System.nanoTime();
        for (Thread t : t1) t.start();
        for (Thread t : t1) t.join();
        long time1 = (System.nanoTime() - start) / 1_000_000;

        start = System.nanoTime();
        for (Thread t : t2) t.start();
        for (Thread t : t2) t.join();
        long time2 = (System.nanoTime() - start) / 1_000_000;

        System.out.println("AtomicLong result: " + atomic.get() + ", time: " + time1 + "ms");
        System.out.println("LongAdder result: " + adder.sum() + ", time: " + time2 + "ms");
    }
}

执行结果典型输出

复制代码
AtomicLong result: 20000000, time: 251ms
LongAdder result: 20000000, time: 68ms

7. 经典陷阱:ABA 问题与解决方案

7.1 什么是 ABA 问题?

场景:你去快递柜取件。

  • 初始:柜子里有包裹 A。
  • 你准备取 A,但手机响了,暂停操作。
  • 此时,别人取走了 A,又放了一个一模一样的 A'(比如同款手机)。
  • 你回来一看:"还是 A 啊",于是取走了。
    但其实中间已经被换过一次了!

在 CAS 中:

  • 线程1 读到值 A;
  • 线程2 把 A → B → A;
  • 线程1 执行 CAS(A, C),成功!但它不知道中间发生了变化。

7.2 解决方案:加"版本号"或"标记位"

Java 提供了两种工具:

机制 适用场景
AtomicStampedReference<T> 每次修改附带一个 int stamp(版本号) 需要精确追踪变更次数
AtomicMarkableReference<T> 附带一个 boolean mark(标记位) 只需知道"是否被修改过"

7.3 示例:用 AtomicStampedReference 避免 ABA

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    public static void main(String[] args) {
        String initialRef = "A";
        int initialStamp = 0;
        AtomicStampedReference<String> ref = new AtomicStampedReference<>(initialRef, initialStamp);

        // 线程1:准备把 A → C,但先读取
        String expectedRef = ref.getReference();
        int expectedStamp = ref.getStamp();
        System.out.println("Thread1 read: " + expectedRef + ", stamp=" + expectedStamp);

        // 模拟中间被修改:A → B → A(stamp 变成 2)
        ref.compareAndSet("A", "B", 0, 1);
        ref.compareAndSet("B", "A", 1, 2);
        System.out.println("After ABA: ref=" + ref.getReference() + ", stamp=" + ref.getStamp());

        // 线程1 尝试 CAS(A, C, stamp=0)
        boolean success = ref.compareAndSet(expectedRef, "C", expectedStamp, 3);
        System.out.println("Thread1 CAS success? " + success);
        System.out.println("Final value: " + ref.getReference() + ", stamp=" + ref.getStamp());
    }
}

输出

复制代码
Thread1 read: A, stamp=0
After ABA: ref=A, stamp=2
Thread1 CAS success? false
Final value: A, stamp=2

说明

  • 虽然值还是 "A",但 stamp 已变,CAS 失败,避免了 ABA 陷阱。
  • 实际开发中,ABA 问题在指针复用(如链表节点回收)场景更常见,普通计数器一般无需担心。

8. 何时使用原子类?性能对比与选型指南

8.1 优势 vs 劣势

维度 原子类(CAS) 锁(synchronized / ReentrantLock)
吞吐量 ⭐⭐⭐⭐⭐(无阻塞) ⭐⭐(线程切换开销大)
延迟 可能波动(重试) 稳定(但有排队延迟)
CPU 开销 高(循环重试消耗 CPU) 低(阻塞时不占 CPU)
复杂度 仅支持单变量原子操作 可保护任意临界区
公平性 无(可能饥饿) 可配置(如公平锁)

8.2 性能实验(简化版)

8.2.1 示例:AtomicInteger vs synchronized 计数器性能对比

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerVsSynchronizedBenchmark {

    // 非原子计数器(仅用于对比错误结果)
    private static int badCounter = 0;

    // synchronized 保护的计数器
    private static int syncCounter = 0;
    private static final Object lock = new Object();

    // 原子计数器
    private static final AtomicInteger atomicCounter = new AtomicInteger(0);

    // 线程安全的 synchronized 增量方法
    public static void incrementSync() {
        synchronized (lock) {
            syncCounter++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final int threadCount = 100;
        final int incrementsPerThread = 10_000;
        final long expectedTotal = (long) threadCount * incrementsPerThread;

        // ===== 测试 synchronized =====
        syncCounter = 0;
        Thread[] threads1 = new Thread[threadCount];
        long start = System.nanoTime();
        for (int i = 0; i < threadCount; i++) {
            threads1[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    incrementSync();
                }
            });
        }
        for (Thread t : threads1) t.start();
        for (Thread t : threads1) t.join();
        long syncTimeNs = System.nanoTime() - start;

        // ===== 测试 AtomicInteger =====
        atomicCounter.set(0);
        Thread[] threads2 = new Thread[threadCount];
        start = System.nanoTime();
        for (int i = 0; i < threadCount; i++) {
            threads2[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    atomicCounter.incrementAndGet();
                }
            });
        }
        for (Thread t : threads2) t.start();
        for (Thread t : threads2) t.join();
        long atomicTimeNs = System.nanoTime() - start;

        // ===== 输出结果 =====
        System.out.println("=== 性能对比:synchronized vs AtomicInteger ===");
        System.out.println("线程数: " + threadCount + ", 每线程操作数: " + incrementsPerThread);
        System.out.println("期望总计数: " + expectedTotal);
        System.out.println();
        System.out.println("[synchronized]");
        System.out.println("  结果: " + syncCounter);
        System.out.println("  正确? " + (syncCounter == expectedTotal));
        System.out.println("  耗时: " + (syncTimeNs / 1_000_000) + " ms");
        System.out.println();
        System.out.println("[AtomicInteger]");
        System.out.println("  结果: " + atomicCounter.get());
        System.out.println("  正确? " + (atomicCounter.get() == expectedTotal));
        System.out.println("  耗时: " + (atomicTimeNs / 1_000_000) + " ms");
        System.out.println();
        double ratio = (double) syncTimeNs / atomicTimeNs;
        System.out.printf("synchronized 耗时是 AtomicInteger 的 %.2f 倍\n", ratio);
    }
}

8.2.2 典型运行结果(JDK 17, 8 核 CPU):

复制代码
=== 性能对比:synchronized vs AtomicInteger ===
线程数: 100, 每线程操作数: 10000
期望总计数: 1000000

[synchronized]
  结果: 1000000
  正确? true
  耗时: 71 ms

[AtomicInteger]
  结果: 1000000
  正确? true
  耗时: 26 ms

synchronized 耗时是 AtomicInteger 的 2.66 倍

💡 说明

  • 在中等并发下(如 100 线程),AtomicInteger 通常快 2~3 倍
  • 并发越高(如 500+ 线程),差距可能扩大到 5 倍以上
  • 若改用 LongAdder,在高并发下还能再快 5~10 倍(见后续示例)。

8.2.3 示例:LongAdder vs AtomicLong 高并发计数性能对比

java 复制代码
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderVsAtomicLongBenchmark {

    public static void main(String[] args) throws InterruptedException {
        // 高并发典型配置:200+ 线程,每线程大量操作
        final int threadCount = 300;
        final int incrementsPerThread = 300_000;
        final long expectedTotal = (long) threadCount * incrementsPerThread;

        // ===== 测试 AtomicLong =====
        AtomicLong atomicLong = new AtomicLong(0);
        Thread[] threads1 = new Thread[threadCount];
        long start = System.nanoTime();
        for (int i = 0; i < threadCount; i++) {
            threads1[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    atomicLong.incrementAndGet();
                }
            });
        }
        for (Thread t : threads1) t.start();
        for (Thread t : threads1) t.join();
        long atomicTimeNs = System.nanoTime() - start;
        long atomicResult = atomicLong.get();

        // ===== 测试 LongAdder =====
        LongAdder longAdder = new LongAdder();
        Thread[] threads2 = new Thread[threadCount];
        start = System.nanoTime();
        for (int i = 0; i < threadCount; i++) {
            threads2[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    longAdder.increment();
                }
            });
        }
        for (Thread t : threads2) t.start();
        for (Thread t : threads2) t.join();
        long adderTimeNs = System.nanoTime() - start;
        long adderResult = longAdder.sum();

        // ===== 输出结果 =====
        System.out.println("=== 性能对比:AtomicLong vs LongAdder(高并发) ===");
        System.out.println("线程数: " + threadCount + ", 每线程操作数: " + incrementsPerThread);
        System.out.println("期望总计数: " + expectedTotal);
        System.out.println();
        System.out.println("[AtomicLong]");
        System.out.println("  结果: " + atomicResult);
        System.out.println("  正确? " + (atomicResult == expectedTotal));
        System.out.println("  耗时: " + (atomicTimeNs / 1_000_000) + " ms");
        System.out.println();
        System.out.println("[LongAdder]");
        System.out.println("  结果: " + adderResult);
        System.out.println("  正确? " + (adderResult == expectedTotal));
        System.out.println("  耗时: " + (adderTimeNs / 1_000_000) + " ms");
        System.out.println();
        if (adderTimeNs > 0) {
            double ratio = (double) atomicTimeNs / adderTimeNs;
            System.out.printf("AtomicLong 耗时是 LongAdder 的 %.2f 倍\n", ratio);
        }
    }
}

8.2.4 典型运行结果(JDK 17, 8 核 CPU, 300 线程):

复制代码
=== 性能对比:AtomicLong vs LongAdder(高并发) ===
线程数: 300, 每线程操作数: 300000
期望总计数: 90000000

[AtomicLong]
  结果: 90000000
  正确? true
  耗时: 1058 ms

[LongAdder]
  结果: 90000000
  正确? true
  耗时: 151 ms

AtomicLong 耗时是 LongAdder 的 7.01 倍

💡 为什么 LongAdder 更快?

  • AtomicLong 所有线程竞争同一个内存地址,CAS 失败率极高,大量重试消耗 CPU;
  • LongAdder 内部使用 @Contended 注解 + 分段计数(cells 数组),每个线程写自己的槽位,最后求和;
  • 空间换时间,极大降低缓存行竞争(Cache Line Contention)。
    ⚠️ 注意:LongAdder 不适合需要实时读取精确值 的场景(如限流),但极其适合统计、监控、指标累加

8.3 选型建议

  • 单变量更新 (计数、状态标志)→ 优先用 AtomicXXX
  • 高并发计数 → 用 LongAdder
  • 多变量协同(如转账:A减、B加)→ 必须用锁;
  • 需要阻塞等待 (如生产者-消费者)→ 用 Lock + Condition

9. 哪些知名数据结构用了原子类?

原子类是并发框架的"地基"。以下是一些典型应用:

数据结构 / 框架 使用的原子类 作用
ConcurrentHashMap (JDK 8+) Unsafe / VarHandle + CAS 替代分段锁,实现无锁扩容、无锁 put
ThreadPoolExecutor AtomicInteger 线程池状态(ctl 字段)、任务计数
Disruptor(高性能队列) Sequence(基于 AtomicLong 生产者/消费者序号无锁推进
Netty AtomicIntegerFieldUpdater Channel 状态、引用计数
CompletableFuture AtomicReferenceFieldUpdater 状态更新、结果设置

💡 这些框架之所以快,底层都离不开 CAS 和原子类


10. 下一篇预告:你还没见过的并发神器

下一篇将聚焦

【Java线程安全实战】⑥ 线程间协作的艺术:Condition、CountDownLatch、CyclicBarrier 与 Phaser 深度剖析

内容亮点:

  • 如何让线程"等一等"、"一起走"、"分阶段执行"?
  • Condition 为何比 wait/notify 更强大?
  • Phaser 如何实现动态阶段控制?
  • 实战:模拟"百米接力赛"、"分布式任务分片"。

敬请期待!


11. 延伸阅读:经典书籍推荐

  1. 《Java并发编程实战》(Java Concurrency in Practice

    • 作者:Brian Goetz 等
    • 地位:Java 并发领域的"圣经"
    • 价值:第 15 章专门讲解非阻塞算法与原子变量,理论扎实,实践指导强。
  2. 《The Art of Multiprocessor Programming》

    • 作者:Maurice Herlihy & Nir Shavit
    • 地位:无锁编程的开山之作
    • 价值:深入讲解 CAS、ABA、无锁队列等底层原理,适合想成为并发专家的读者。

📚 虽然出版较早,但其思想至今仍是现代并发库(包括 Java、Go、Rust)的设计基石。


12. 参考链接

前面四篇我们讲了:


✍️ 作者注:本文所有技术细节均基于 OpenJDK 源码(JDK 8 / 17 / 21),无虚构内容。如有疑问,欢迎留言讨论。


13. 总结

通过本文,你不仅理解了:

  • 原子类为何诞生(解决轻量级同步);
  • CAS 如何工作(硬件级原子指令);
  • AtomicInteger 源码如何循环重试;
  • LongAdder 为何更快(分段累加);
  • ABA 问题如何用 StampedReference 解决;
  • Java 是如何调用CPU的原子操作的?
  • 还亲手运行了三个可验证的 main 示例

这正是无锁编程的魅力:不靠排队,靠"聪明地重试"。


✍️ 作者注:所有代码均可在 JDK 8+ 直接运行。Mermaid 类图建议用支持渲染的编辑器查看。本文无虚构内容,技术细节均来自 OpenJDK 源码。

相关推荐
西敏寺的乐章18 小时前
ThreadLocal / InheritableThreadLocal / TransmittableThreadLocal(TTL)学习总结
java·开发语言·网络
深盾科技18 小时前
C++ 中 std::error_code 的应用与实践
java·前端·c++
多米Domi01118 小时前
0x3f 第20天 三更24-32 hot100子串
java·python·算法·leetcode·动态规划
人道领域18 小时前
【零基础学java】(不可变集合)
java
独自归家的兔18 小时前
基于 Doubao-Seedream-4.5 的单张图片生成后端接口实战
java·人工智能·spring boot·后端
C++chaofan18 小时前
JUC 中 synchronized 的底层实现原理解析——Monitor
java·开发语言·c++·rust·ruby·juc·字节码
刘一说18 小时前
Tomcat在Spring Boot集成原理及优化应用:深度解析与实战指南
java·spring boot·tomcat
禹中一只鱼19 小时前
【SpringBoot 配置文件】
java·spring boot·后端
sayang_shao19 小时前
C++ 引用【笔记】
java·c++·笔记