JDK Atomic 设计及工作原理与教程

📅 2026/7/7 9:36:46
JDK Atomic 设计及工作原理与教程
JDK 的java.util.concurrent.atomic包是 Java 并发编程的基石。它提供了一套线程安全、非阻塞的操作单个变量的工具类核心设计理念是无锁Lock-Free利用底层硬件指令来替代重量级的synchronized锁。下面将从设计哲学、核心工作原理和实战教程三个维度为你深度解析。一、 设计哲学CAS 可见性 失败重试Atomic 包的设计并非简单地对方法加锁而是围绕以下三大支柱构建CASCompare-And-Swap比较并交换这是整个包的底层基石。它是一个 CPU 原子指令如 x86 的CMPXCHG它比较内存中的值与预期值如果相等则用新值替换否则不做任何操作。整个过程是硬件级别的原子操作。volatile 可见性Atomic 类中的value字段都被volatile修饰确保任何线程修改值后其他线程能立刻看到最新值。自旋Spin重试当 CAS 操作失败说明有并发修改线程不会挂起而是在循环中不断重试直到成功为止。这避免了线程上下文切换的开销。二、 核心工作原理源码级剖析以最常用的AtomicInteger为例深入源码看其内部机制1. 关键属性Unsafe 魔法// 真正的值存储在 volatile 字段中 private volatile int value; // Unsafe 类提供底层硬件级别操作能力 private static final Unsafe unsafe Unsafe.getUnsafe(); // value 字段在内存中的偏移量通过 Unsafe 提前计算好 private static final long valueOffset;2. 自增操作incrementAndGet的底层流程public final int incrementAndGet() { // 无限循环自旋 return unsafe.getAndAddInt(this, valueOffset, 1) 1; } // Unsafe 中的核心逻辑简化伪代码 public final int getAndAddInt(Object obj, long offset, int delta) { int expected; do { // 1. 获取当前内存中的旧值此时 value 是 volatile 的 expected this.getIntVolatile(obj, offset); // 2. 尝试 CAS 交换如果内存值此刻仍等于 expected则更新为 expected delta // 3. 如果失败说明被其他线程改过了循环重试 } while (!this.compareAndSwapInt(obj, offset, expected, expected delta)); return expected; }工作流程图解线程 A 读取value1- 线程 B 此时将value改为2- 线程 A 执行 CAS 时发现内存值是2而非预期的1- CAS 返回 False - 线程 A 重新读取value2并尝试改为3- CAS 成功。三、 Atomic 包分类速览实战类型分类代表类适用场景基础原子类AtomicInteger,AtomicLong,AtomicBoolean替代int/long的计数器、状态标志位。数组原子类AtomicIntegerArray,AtomicReferenceArray需要原子更新数组中某个元素时使用。引用原子类AtomicReference,AtomicStampedReference解决 CAS 的ABA 问题后者带版本戳。字段更新器AtomicReferenceFieldUpdater对已有对象中的某个volatile字段进行原子更新节省内存不创建新对象。高性能累加器LongAdder,LongAccumulator高并发写多读少的统计场景性能远超AtomicLong采用分段累加最后汇总。四、 实战教程代码 避坑指南场景1安全计数器基础用法public class AtomicCounter { private AtomicInteger count new AtomicInteger(0); public void increment() { // 推荐使用 getAndIncrement() 或 incrementAndGet() count.incrementAndGet(); } public int get() { return count.get(); } }场景2解决 ABA 问题使用AtomicStampedReferenceABA 问题线程1 将 A 改为 B 再改回 A线程2 执行 CAS 时误以为值没变过。// 参数初始值 A初始版本号 1 AtomicStampedReferenceString ref new AtomicStampedReference(A, 1); // 正确的更新方式必须同时匹配值和版本号 boolean success ref.compareAndSet(A, B, ref.getStamp(), // 期望版本号 ref.getStamp() 1 // 新版本号 );场景3高并发统计使用LongAdder代替AtomicLong// 适用于统计 QPS、请求总数且对实时性要求不极端苛刻的场景 LongAdder adder new LongAdder(); // 并发执行增加 adder.increment(); // 最终汇总注意sum() 不是实时精确值但在高并发下误差极小 long total adder.sum();五、 核心局限性与面试高频坑点CAS 自旋开销如果并发冲突极高极端竞争CAS 会频繁失败并重试导致 CPU 飙升。此时应回退使用synchronized或LongAdder。仅能保证单个变量原子性如果需要同时更新多个变量CAS 无能为力此时必须使用synchronized或ReentrantLock包裹代码块。ABA 问题虽然基本数值类型如 int的 ABA 不影响业务数值相等即可但引用类型如对象必须考虑使用AtomicStampedReference或AtomicMarkableReference解决。六、 学习路线建议循序渐进初阶手写一个AtomicInteger的自旋锁 Demo理解compareAndSet的返回值含义。中阶结合ConcurrentHashMap源码看它如何利用AtomicInteger计算 size以及如何利用AtomicReference管理 Node 链表头。高阶剖析LongAdder的Cell数组设计理解空间换时间和伪共享False Sharing的sun.misc.Contended注解原理。如果你准备面试极大概率会被问到“AtomicInteger 和 volatile 的区别是什么”我的总结是volatile解决的是可见性但无法解决复合操作如 i的原子性而AtomicInteger利用 CAS 既保证了可见性又保证了复合操作的原子性。