编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

    • 综合案例

    • 专栏博客

      • README
      • JVM内存模型与对象
      • 类加载与双亲委派
      • 垃圾回收与GC调优
      • 异常体系与JVM机制
      • 字节码指令集javap实战
      • JIT编译与去优化机制
      • JVM性能诊断工具链
      • OOM八大现场全景剖析
      • JVM参数调优全景图
      • GraalVM与AOT编译原理
      • HashMap底层哈希设计
      • String不可变与常量池
      • ArrayList与LinkedList源码
      • ConcurrentHashMap并发
      • TreeMap与红黑树原理
      • LinkedHashMap与LRU实现
      • Java数字类型原理
      • Object通用方法的契约
      • 泛型擦除与类型系统
      • 枚举原理与最佳实践
      • 注解原理与编译期处理
      • Lambda与引用底层原理
      • Stream原理与流水线设计
      • Optional设计原理
      • Record密封类与模式
      • 反射机制与动态代理
      • MethodHandle与VarHandle
      • 三大字节码框架对比
      • JavaAgent与Instrumentation机制
      • AOP三种实现路线对比
      • synchronized与锁升级
      • volatile与JMM内存模型
      • 线程池核心源码设计
      • Thread线程生命周期
      • AQS同步框架源码
      • 并发锁三剑客
      • CAS和Atomic深入分析
        • 1. 案例引入
          • 1.1 AtomicLong 高并发的吞吐天花板
          • 1.2 ABA 引发的转账幽灵 bug
          • 1.3 我们要回答什么
        • 2. CAS 的物理基础
          • 2.1 CAS 三要素与伪代码语义
          • 2.2 LOCK CMPXCHG:x86 的原子保证
          • 2.3 缓存一致性 MESI 与 CAS 的代价
          • 2.4 ARM 的 LL/SC 原语对比
          • 2.5 自旋的开销与上限
        • 3. Atomic 全家桶源码剖析
          • 3.1 四大类家族总览
          • 3.2 AtomicInteger 核心源码
          • 3.3 AtomicReference 与泛型
          • 3.4 AtomicIntegerArray 数组原子
          • 3.5 AtomicXxxFieldUpdater 反射大法
        • 4. ABA 问题与解决
          • 4.1 ABA 的本质:值相等 ≠ 状态未变
          • 4.2 AtomicStampedReference 版本号方案
          • 4.3 AtomicMarkableReference 单标志方案
          • 4.4 工程视角:哪些场景必须防 ABA
        • 5. LongAdder & Striped64 分段累加革命
          • 5.1 AtomicLong 高并发瓶颈的根因
          • 5.2 LongAdder 核心思想:化整为零
          • 5.3 Striped64 数据结构
          • 5.4 add 方法热点路径
          • 5.5 Cell 动态扩容机制
          • 5.6 @Contended 伪共享防护
          • 5.7 sum 方法的弱一致性
          • 5.8 LongAccumulator/DoubleAdder 兄弟
        • 6. Unsafe 时代的"危险但强大"
          • 6.1 Unsafe 五大能力域
          • 6.2 compareAndSwapXxx:CAS 的本尊
          • 6.3 park/unpark:Lock/AQS 的物理基础
          • 6.4 allocateMemory 与堆外内存
          • 6.5 Unsafe 的获取黑魔法
        • 7. VarHandle 取代 Unsafe
          • 7.1 JDK 9 模块化对 Unsafe 的封锁
          • 7.2 VarHandle 在 Atomic 中的落地
          • 7.3 与 31 篇的衔接:四级访问模式
          • 7.4 Unsafe → VarHandle 迁移指南
        • 8. 性能实证
          • 8.1 JMH 测试场景
          • 8.2 单线程基线
          • 8.3 16 线程高竞争
          • 8.4 64 线程极端竞争
          • 8.5 选型决策树
        • 9. 综合回扣与设计哲学
          • 9.1 案例真相揭晓
          • 9.2 三大设计哲学
          • 9.3 速查表与下一篇预告
      • 五大同步器对比
      • CompletableFuture异步
      • IO模型演进BIO到AIO
      • ByteBuffer与堆外内存
      • 序列化原理与替代方案
      • 文件IO与NIO.2
      • 面向对象的真意
      • JDK设计模式上
      • JDK设计模式下
      • SPI与模块化设计
  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 专栏博客
杨充
2026-06-02
目录

CAS和Atomic深入分析

# 37.CAS和Atomic深入分析

# 目录介绍

  • 1. 案例引入
    • 1.1 AtomicLong 高并发的吞吐天花板
    • 1.2 ABA 引发的转账幽灵 bug
    • 1.3 我们要回答什么
  • 2. CAS 的物理基础
    • 2.1 CAS 三要素与伪代码语义
    • 2.2 LOCK CMPXCHG:x86 的原子保证
    • 2.3 缓存一致性 MESI 与 CAS 的代价
    • 2.4 ARM 的 LL/SC 原语对比
    • 2.5 自旋的开销与上限
  • 3. Atomic 全家桶源码剖析
    • 3.1 四大类家族总览
    • 3.2 AtomicInteger 核心源码
    • 3.3 AtomicReference 与泛型
    • 3.4 AtomicIntegerArray 数组原子
    • 3.5 AtomicXxxFieldUpdater 反射大法
  • 4. ABA 问题与解决
    • 4.1 ABA 的本质:值相等 ≠ 状态未变
    • 4.2 AtomicStampedReference 版本号方案
    • 4.3 AtomicMarkableReference 单标志方案
    • 4.4 工程视角:哪些场景必须防 ABA
  • 5. LongAdder & Striped64 分段累加革命
    • 5.1 AtomicLong 高并发瓶颈的根因
    • 5.2 LongAdder 核心思想:化整为零
    • 5.3 Striped64 数据结构
    • 5.4 add 方法热点路径
    • 5.5 Cell 动态扩容机制
    • 5.6 @Contended 伪共享防护
    • 5.7 sum 方法的弱一致性
    • 5.8 LongAccumulator/DoubleAdder 兄弟
  • 6. Unsafe 时代的"危险但强大"
    • 6.1 Unsafe 五大能力域
    • 6.2 compareAndSwapXxx:CAS 的本尊
    • 6.3 park/unpark:Lock/AQS 的物理基础
    • 6.4 allocateMemory 与堆外内存
    • 6.5 Unsafe 的获取黑魔法
  • 7. VarHandle 取代 Unsafe
    • 7.1 JDK 9 模块化对 Unsafe 的封锁
    • 7.2 VarHandle 在 Atomic 中的落地
    • 7.3 与 31 篇的衔接:四级访问模式
    • 7.4 Unsafe → VarHandle 迁移指南
  • 8. 性能实证
    • 8.1 JMH 测试场景
    • 8.2 单线程基线
    • 8.3 16 线程高竞争
    • 8.4 64 线程极端竞争
    • 8.5 选型决策树
  • 9. 综合回扣与设计哲学
    • 9.1 案例真相揭晓
    • 9.2 三大设计哲学
    • 9.3 速查表与下一篇预告

# 1. 案例引入

# 1.1 AtomicLong 高并发的吞吐天花板

某金融风控系统的全局请求计数器,用 AtomicLong 实现:"不就是个原子自增吗,CAS 妥妥的零锁":

public class RequestCounter {
    private final AtomicLong count = new AtomicLong();
    public void inc() { count.incrementAndGet(); }
    public long get() { return count.get(); }
}
1
2
3
4
5

单机 4 核压测无压力——100 万 QPS。上线后扩容到 32 核,期望线性提升到 800 万——实测只有 280 万 QPS,CPU 却被打满。perf top 一看:

32.5%  java   __cmpxchg16b      ← CAS 重试
18.2%  java   AtomicLong.incrementAndGet
 ...
1
2
3

32 核机器上 32 个线程都在同一个内存地址上 CAS——绝大多数时间在 CAS 失败 + 重试。换成 LongAdder:

private final LongAdder count = new LongAdder();
public void inc() { count.increment(); }
public long get() { return count.sum(); }
1
2
3

实测 2400 万 QPS,CPU 降到 60%——吞吐提升 8 倍。同样是无锁,差距为什么这么大?这是 §5 要回答的核心。

# 1.2 ABA 引发的转账幽灵 bug

某支付系统用 AtomicReference<Account> 做余额无锁更新,"反正 CAS 能保证原子性":

AtomicReference<Account> ref = new AtomicReference<>(acc);
Account old = ref.get();
Account neu = old.withBalance(old.balance - 100);
boolean ok = ref.compareAndSet(old, neu);    // ← CAS 失败就重试
1
2
3
4

某天对账:1000 笔扣款,账上只少了 800——20 万元凭空消失。

排查发现:高并发下出现这种交错:

T1: 读 old = Acc(100元)              → 准备扣 100
T2: 读 acc = Acc(100元)              → 扣 100 → ref = Acc(0元)
T2: 又来 100 元入账 → ref = Acc(100元) ← 同一个引用对象池复用
T1: CAS(old=Acc(100), neu=Acc(0))    ← old.balance=100 == 当前 100 → 成功!
                                       T2 的入账被覆盖丢失!
1
2
3
4
5

CAS 比的是"地址相等",但同一地址的对象状态可能已经"绕了一圈又回到了起点"——这就是 ABA 问题。compareAndSet 看似成功,业务实际丢了 200 元。怎么破? —— §4 给出标准方案。

# 1.3 我们要回答什么

38 篇是卷五第 7 篇,沉到 36/37 篇 Lock 之下、CPU 指令之上的"无锁原语"层:

37.Lock 三剑客 (用户态阻塞)  →  38.CAS/Atomic (本篇/无锁原语)  →  39.五大同步器
                                       │
                                       ↓
                       Lock/AQS 内部用的全是 CAS(36 篇 §3 见过)
1
2
3
4

带 6 个追问展开:

追问 ①:CAS 凭什么是原子的?JVM 怎么把它落到 CPU 指令?           → §2
追问 ②:Atomic 全家桶怎么覆盖整数/对象/数组/字段四种场景?        → §3
追问 ③:ABA 问题为什么必须重视?正确防护代价多少?                → §4
追问 ④:LongAdder 凭什么比 AtomicLong 在高竞争下快 10 倍?         → §5
追问 ⑤:Unsafe 那么强为什么要被 VarHandle 取代?                  → §6 + §7
追问 ⑥:什么时候用 AtomicLong,什么时候必须换 LongAdder?          → §8
1
2
3
4
5
6

# 2. CAS 的物理基础

# 2.1 CAS 三要素与伪代码语义

CAS = Compare And Swap,三个参数:

CAS(addr, expected, newValue):
    if (*addr == expected):
        *addr = newValue
        return true
    else:
        return false
1
2
3
4
5
6

关键:上面这"读 → 比较 → 写"三步必须原子完成——不能被任何中断、任何其他 CPU 的写打断。这不是普通的 if-else,是硬件层面的原子性。

如果用普通指令实现:

LOAD  R1, [addr]       ← 读
CMP   R1, expected     ← 比
                       ← ⚠️ 此时其他 CPU 改了 *addr 怎么办?
STORE [addr], newValue ← 写
1
2
3
4

中间任意一步都可能被打断——所以 CAS 必须是单条原子指令,靠 CPU 硬件保证。

# 2.2 LOCK CMPXCHG:x86 的原子保证

x86 平台上,CAS 落到一条指令——CMPXCHG,前面加 LOCK 前缀强制原子:

lock cmpxchgq %rdx, (%rdi)   ; if (*rdi == %rax) *rdi = %rdx; else %rax = *rdi
1

LOCK 前缀做了什么?早期是锁总线——这条指令期间整条内存总线被独占,其他 CPU 的内存访问全卡住。现代 CPU(Pentium Pro 以后)改为缓存锁定——只锁住该内存地址所在的缓存行(cache line),其他 CPU 访问无关地址不受影响,效率更高。

OpenJDK HotSpot 中 Atomic::cmpxchg 在 x86 的实现:

// hotspot/src/os_cpu/linux_x86/atomic_linux_x86.hpp
template<>
inline jint Atomic::cmpxchg<jint>(jint exchange_value, volatile jint* dest, jint compare_value) {
    __asm__ volatile ("lock cmpxchgl %1, (%3)"
                      : "=a"(exchange_value)
                      : "r"(exchange_value), "a"(compare_value), "r"(dest)
                      : "cc", "memory");
    return exchange_value;
}
1
2
3
4
5
6
7
8
9

lock cmpxchgl 一条指令 = Java 层 compareAndSet 的全部物理实现。任意 Java 的 CAS 调用,最终都收敛到这条 CPU 指令。

# 2.3 缓存一致性 MESI 与 CAS 的代价

CAS 不是"免费"的。理解它的代价必须懂 MESI 缓存一致性协议:

M (Modified)  :本核独占且已修改,主存数据是脏的
E (Exclusive) :本核独占未修改
S (Shared)    :多核共享只读
I (Invalid)   :无效(被其他核修改了)
1
2
3
4

CAS 一条指令的真实开销:

1. 把 cache line 从 S 升级到 M(需要 RFO - Read For Ownership 请求)
2. 广播 Invalidate 给其他核(其他核的副本失效)
3. 等所有核 ACK 回来
4. 执行 cmpxchg
5. cache line 状态变为 M
1
2
3
4
5

热点 CAS = 全核间不停广播 Invalidate——这就是 §1.1 中 32 核 AtomicLong 翻车的根因:32 核都在抢同一个 cache line 的所有权,每次 CAS 都触发全局缓存一致性风暴。

# 2.4 ARM 的 LL/SC 原语对比

ARM/POWER 等 RISC 架构没有 CAS 单指令,用 LL/SC(Load-Linked / Store-Conditional) 模拟:

loop:
    LDREX  R1, [addr]      ; LL:读并打标记
    CMP    R1, expected
    BNE    fail
    STREX  R2, newValue, [addr]  ; SC:仅当标记仍在才写成功,R2=0
    CBNZ   R2, loop        ; SC 失败则重试
1
2
3
4
5
6

特点:LL/SC 通过"读时打标记 + 写时检查标记"实现原子性,标记被任何写操作清除——天然抵抗 ABA(在硬件层面)。但 Java 层暴露的还是 CAS 语义——JVM 在 ARM 上用 LL/SC 模拟 CAS,对 Java 程序员透明。

这是为什么 ARM 上同样的 Java 代码在写多场景下表现可能略好——LL/SC 不需要 RFO 独占 cache line。

# 2.5 自旋的开销与上限

CAS 失败后通常自旋重试:

public final int incrementAndGet() {
    int prev, next;
    do {
        prev = get();
        next = prev + 1;
    } while (!compareAndSet(prev, next));   // ← 失败就重试
    return next;
}
1
2
3
4
5
6
7
8

自旋的代价:

  • CPU 空转——重试期间消耗 CPU 时间片
  • Cache line 抢夺——每次重试都重新拉缓存行
  • 能量浪费——移动设备发热

自旋什么时候划算:临界区极短(几条指令)+ 竞争不激烈(< 4 线程同时)。否则就该用锁——这是 36/37 篇 Lock 存在的根本原因。LongAdder 的设计目标就是让 CAS 自旋次数下降一个数量级(§5)。

# 3. Atomic 全家桶源码剖析

# 3.1 四大类家族总览

java.util.concurrent.atomic 包按用途分四大类:

类别 代表类 用途
基本类型 AtomicInteger / AtomicLong / AtomicBoolean 单个原子变量
引用类型 AtomicReference / AtomicStampedReference / AtomicMarkableReference 对象引用原子
数组类型 AtomicIntegerArray / AtomicLongArray / AtomicReferenceArray 数组元素原子
字段更新器 AtomicIntegerFieldUpdater / AtomicLongFieldUpdater / AtomicReferenceFieldUpdater 已有类的字段改造为原子
累加器(§5) LongAdder / LongAccumulator / DoubleAdder / DoubleAccumulator 高竞争下的优化版

# 3.2 AtomicInteger 核心源码

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final VarHandle VALUE;                  // ← JDK 9 起换 VarHandle
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            VALUE = l.findVarHandle(AtomicInteger.class, "value", int.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    private volatile int value;                            // ★ volatile 保证可见性

    public final int get() { return value; }               // 直接读 volatile
    public final void set(int newValue) { value = newValue; }   // 直接写 volatile

    public final boolean compareAndSet(int expectedValue, int newValue) {
        return VALUE.compareAndSet(this, expectedValue, newValue);  // ★ VarHandle.CAS
    }

    public final int incrementAndGet() {
        return (int) VALUE.getAndAdd(this, 1) + 1;          // ★ JDK 8 还是手写 CAS 自旋
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

JDK 7 → JDK 8 → JDK 9 的演进:

  • JDK 7-:自己写 CAS 自旋循环
  • JDK 8:Unsafe.getAndAddInt 内部仍是自旋,但部分平台编译为 LOCK XADD 单指令(intrinsic 优化)
  • JDK 9+:底层换为 VarHandle,逻辑不变但符合模块化(§7)
// JDK 8 Unsafe.getAndAddInt 内部
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));   // ← 失败重试
    return v;
}
1
2
3
4
5
6
7
8

注意:HotSpot 对 getAndAddInt 有 intrinsic 替换——若 CPU 支持 LOCK XADD(x86 自带),整个循环会被替换为一条 lock xaddl 指令,无需软件自旋。这是为什么 incrementAndGet 在 x86 上比看起来快。

# 3.3 AtomicReference 与泛型

public class AtomicReference<V> implements java.io.Serializable {
    private static final VarHandle VALUE;
    private volatile V value;

    public final boolean compareAndSet(V expectedValue, V newValue) {
        return VALUE.compareAndSet(this, expectedValue, newValue);
    }
}
1
2
3
4
5
6
7
8

注意:compareAndSet(expected, new) 比的是引用相等(==),不是 equals!这就是 §1.2 ABA 问题的根源——同一引用值的对象内部状态可能已变化。

典型用法:无锁链表:

class LockFreeStack<T> {
    private final AtomicReference<Node<T>> top = new AtomicReference<>();

    public void push(T item) {
        Node<T> oldTop, newTop;
        do {
            oldTop = top.get();
            newTop = new Node<>(item, oldTop);
        } while (!top.compareAndSet(oldTop, newTop));   // ← 经典 CAS 重试
    }

    public T pop() {
        Node<T> oldTop, newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) return null;
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop));
        return oldTop.item;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

警告:上面的 pop 在节点对象池场景下会发生 ABA 问题——节点出栈后又重用入栈,CAS 仍成功但中间状态丢失。Treiber Stack 必须改用 AtomicStampedReference(§4.2)。

# 3.4 AtomicIntegerArray 数组原子

数组的特殊性:单元素原子操作,不是整个数组:

public class AtomicIntegerArray implements java.io.Serializable {
    private static final VarHandle AA = MethodHandles.arrayElementVarHandle(int[].class);
    private final int[] array;

    public final boolean compareAndSet(int i, int expectedValue, int newValue) {
        return AA.compareAndSet(array, i, expectedValue, newValue);
    }
}
1
2
3
4
5
6
7
8

性能注意:相邻数组元素可能落在同一 cache line(一个 cache line 通常 64 字节,可装 16 个 int)——多线程操作相邻索引会引发伪共享,性能比独立 AtomicInteger 还差。这正是 LongAdder 用 @Contended 防护的原因(§5.6)。

# 3.5 AtomicXxxFieldUpdater 反射大法

场景:已有的类用了 volatile int counter,现在想原子更新——不想改成 AtomicInteger(侵入性大)。

class Connection {
    volatile int state;          // ← 已有字段,不能动
    
    private static final AtomicIntegerFieldUpdater<Connection> STATE_UPDATER =
        AtomicIntegerFieldUpdater.newUpdater(Connection.class, "state");

    public boolean tryClose() {
        return STATE_UPDATER.compareAndSet(this, OPEN, CLOSED);
    }
}
1
2
3
4
5
6
7
8
9
10

好处:

  • 零内存开销:每实例不需要额外的 AtomicInteger 包装对象(节省一个对象头 + 一个引用)
  • 遗留代码改造友好:不需要重构整个类

限制:

  • 字段必须是 volatile
  • 字段不能是 private(JDK 9 之前需要包私有以上可见性)
  • 反射开销(创建 Updater 时一次性,后续 CAS 无开销)

典型用例:Netty 的 ChannelPromise、AQS 的 state 字段早期都用过 FieldUpdater;JDK 9+ 后内部改为 VarHandle,但 FieldUpdater 仍保留兼容老代码。

# 4. ABA 问题与解决

# 4.1 ABA 的本质:值相等 ≠ 状态未变

ABA 经典三步剧本:

T1: 读取 X,期望值 = A
T2: X: A → B → A   (改了又改回来)
T1: CAS(X, A, C)  → 成功(误以为 X 没动过)
1
2
3

为什么是问题:CAS 的语义是"基于'我读到的状态'做更新"——但状态可能已经历完整周期。在以下场景里 ABA 会引发真实 bug:

✗ 对象池复用 :节点出池入池,地址重用
✗ 链表无锁    :Treiber Stack 弹出 + 推入相同节点
✗ 余额扣款    :§1.2 中入账被覆盖
1
2
3

特殊情况下 ABA 不是问题:纯计数器(AtomicLong 自增)——只关心最终值的正确性,中间过程不重要。所以不是所有 CAS 都需要防 ABA,要看业务语义。

# 4.2 AtomicStampedReference 版本号方案

用一个版本戳伴随引用——每次 CAS 时同时检查值和戳,戳每次更新单调递增:

public class AtomicStampedReference<V> {
    private static class Pair<T> {
        final T reference;
        final int stamp;
    }
    private volatile Pair<V> pair;

    public boolean compareAndSet(V expectedReference, V newReference,
                                 int expectedStamp, int newStamp) {
        Pair<V> current = pair;
        return expectedReference == current.reference &&
               expectedStamp == current.stamp &&             // ★ 同时检查戳
               ((newReference == current.reference &&
                 newStamp == current.stamp) ||
                casPair(current, Pair.of(newReference, newStamp)));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

用法:

AtomicStampedReference<Account> ref = new AtomicStampedReference<>(acc, 0);

int[] stampHolder = new int[1];
Account old = ref.get(stampHolder);
int oldStamp = stampHolder[0];
Account neu = old.withBalance(old.balance - 100);

boolean ok = ref.compareAndSet(old, neu, oldStamp, oldStamp + 1);   // ★ 戳 +1
1
2
3
4
5
6
7
8

戳的作用:哪怕引用又绕回 A,戳早已不是原来的 oldStamp——CAS 失败重试,避免幽灵 bug。这就是 §1.2 的标准修复。

类比:MVCC 的事务版本号、Git 的 commit hash、HTTP 的 ETag——都是同一个思路:用单调递增的版本号区分"看似相同的状态"。

# 4.3 AtomicMarkableReference 单标志方案

如果只关心"被改过没改过",不关心改了多少次——用一位 boolean 比一个 int 更省:

public class AtomicMarkableReference<V> {
    private static class Pair<T> {
        final T reference;
        final boolean mark;
    }

    public boolean compareAndSet(V expectedReference, V newReference,
                                 boolean expectedMark, boolean newMark) {
        // 同时比较 reference + mark
    }
}
1
2
3
4
5
6
7
8
9
10
11

典型场景:无锁链表的"逻辑删除"——节点上挂一个 mark=true 表示已删除,物理移除推迟到下次 CAS:

// CompletableFuture 的内部就用了类似的 mark 机制
node.casNext(oldNext, newNext, oldMark, true)   // 标记删除
1
2

# 4.4 工程视角:哪些场景必须防 ABA

✅ 必须防 ABA:
   - 对象池 / 节点池 → 用 AtomicStampedReference
   - 无锁数据结构(Stack/Queue)→ 同上
   - 状态机有"绕回"语义(如 A → B → A 是合法转换)

❌ 不需要防 ABA:
   - 纯计数器(AtomicLong 自增)
   - 单调递增的时间戳/序号
   - 状态机不会绕回(如 OPEN → CLOSED 不可逆)
1
2
3
4
5
6
7
8
9

经验法则:业务上"是否经历过 B 状态"会影响后续行为 → 必须防;反之可以省。

# 5. LongAdder & Striped64 分段累加革命

# 5.1 AtomicLong 高并发瓶颈的根因

回到 §1.1:32 核机器 32 线程对同一个 AtomicLong 自增:

所有线程都在抢同一个 cache line:
        ┌────────────────────────┐
        │ AtomicLong.value (8B)  │  ← 32 核都在 CAS 同一地址
        └────────────────────────┘
              ↑
        ┌─────┴────────────────────────┐
        │  Core0  Core1  Core2 ... Core31 │
        └──────────────────────────────┘

每个 CAS 都触发 RFO + Invalidate 广播 → 缓存一致性风暴
1
2
3
4
5
6
7
8
9
10

AtomicLong 高竞争下 CAS 失败率极高——理论上无锁,实际上"软件锁"(自旋等价于忙等待)。

# 5.2 LongAdder 核心思想:化整为零

LongAdder 的核心思想可以一句话概括:

不要让所有线程都改同一个变量——把热点拆成 N 个,每个线程只改自己那一份;要总和时再加起来。

AtomicLong:               LongAdder:
                          
  [ value ]  ←──── 32 核    base                  ← 低竞争时用 base
                            [Cell0][Cell1]...[Cell15]
                              ↑     ↑          ↑
                             T0    T1   ...   T31    ← 高竞争时用 Cell 数组
                             
                           sum() = base + sum(Cells)
1
2
3
4
5
6
7
8

代价:读取总和(sum)不再是 O(1)——需要遍历所有 Cell 累加,且不是强一致(遍历期间可能有 Cell 被写入)。收益:写吞吐线性可扩展——核数翻倍,吞吐近乎翻倍。

这是典型的"用读时复杂换写时吞吐"trade-off——和 ConcurrentHashMap 分段、Java 7 ForkJoinPool 队列分段一脉相承。

# 5.3 Striped64 数据结构

LongAdder 继承自 Striped64(DoubleAdder 也继承它),核心字段:

abstract class Striped64 extends Number {
    transient volatile Cell[] cells;       // ★ Cell 数组(懒初始化)
    transient volatile long base;          // ★ 基础值(无竞争时直接累加这里)
    transient volatile int cellsBusy;      // ★ 自旋锁(保护 cells 扩容/初始化)

    @jdk.internal.vm.annotation.Contended
    static final class Cell {              // ★ 单元格 + 伪共享防护
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) { return VALUE.compareAndSet(this, cmp, val); }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

关键设计:

  • base —— 无竞争快路径
  • cells[] —— 高竞争时分散到多个槽位
  • cellsBusy —— 1 字节自旋锁,仅保护数组创建/扩容
  • Cell 用 @Contended 注解避免伪共享(§5.6)

# 5.4 add 方法热点路径

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        // ★ 走到这里说明:① cells 已建(高竞争已发生)或 ② CAS base 失败一次
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||              // ★ 用线程探针选 Cell
            !(uncontended = c.cas(v = c.value, v + x))) {     // ★ CAS Cell.value
            longAccumulate(x, null, uncontended);             // ↑ 失败 → 慢路径(扩容/重定位)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

双重快慢路径:

  1. 极速路径:casBase(base, base + x) 一把成功 → 完事(无竞争场景)
  2. 快路径:用 getProbe() & (cells.length - 1) 选 Cell,CAS 成功 → 完事
  3. 慢路径:longAccumulate —— Cell 扩容、线程探针重设、自旋抢 cellsBusy

线程探针 getProbe():每个线程从 Thread.threadLocalRandomProbe 取一个伪随机值——保证不同线程大概率分布到不同 Cell。

# 5.5 Cell 动态扩容机制

longAccumulate 的核心扩容逻辑(简化):

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) { ThreadLocalRandom.current(); h = getProbe(); }   // ① 初始化探针
    boolean collide = false;                                                       // ② 是否需要扩容
    for (;;) {
        Cell[] cs; Cell c; int n; long v;
        if ((cs = cells) != null && (n = cs.length) > 0) {
            if ((c = cs[(n - 1) & h]) == null) {            // ③ 该位置空 → 创建 Cell
                if (cellsBusy == 0 && casCellsBusy()) {     //    抢自旋锁
                    try { /* 创建并放入 */ } finally { cellsBusy = 0; }
                }
            } else if (c.cas(v = c.value, v + x)) break;     // ④ CAS 成功 → 完事
            else if (n >= NCPU || cells != cs) collide = false;  // ⑤ 已到 CPU 数上限,不扩容
            else if (!collide) collide = true;
            else if (cellsBusy == 0 && casCellsBusy()) {    // ⑥ 扩容:长度翻倍
                try { Cell[] rs = new Cell[n << 1]; ... } finally { cellsBusy = 0; }
                collide = false;
            }
            h = advanceProbe(h);                             // ⑦ 重设探针,下轮换槽
        } else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
            // ⑧ 第一次初始化 cells 数组(默认 size = 2)
            try { cells = new Cell[]{ new Cell(x) }; } finally { cellsBusy = 0; }
            break;
        } else if (casBase(v = base, v + x)) break;          // ⑨ 兜底:直接累加 base
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

关键设计点:

  • 数组长度上限 = NCPU(CPU 核心数向上 2 的幂)—— 超过就没意义了
  • 2 倍扩容 —— 类似 HashMap,& 运算定位槽位
  • collide 标志 —— 连续两次冲突才扩容,避免抖动
  • probe 重设 —— 同线程多次重试时换槽,分散竞争

# 5.6 @Contended 伪共享防护

伪共享(False Sharing):两个独立变量恰好落在同一 cache line,多核分别更新时仍引发 cache line 抢夺——明明是独立变量,性能却像在抢同一个变量。

没有 @Contended:
  cache line (64B) ┌────────────┬────────────┬────────────┐
                   │ Cell0.value│ Cell1.value│ Cell2.value│
                   └────────────┴────────────┴────────────┘
                        ↑              ↑              ↑
                       Core0         Core1         Core2
                   ⚠️ 三个核相互 invalidate,伪共享触发
1
2
3
4
5
6
7

@Contended 解决方案:在 Cell 前后填充字节,确保每个 Cell 独占一条 cache line:

@jdk.internal.vm.annotation.Contended
static final class Cell {
    volatile long value;
}
// 实际内存布局(JVM 处理):
//   [前 128B padding][value 8B][后 128B padding]
// → 每个 Cell 占用 ~144B,独占两条 cache line
1
2
3
4
5
6
7

JDK 启动参数:@Contended 需要 -XX:-RestrictContended 才能在用户代码生效(默认仅 JDK 内部类启用)。自定义类要用伪共享防护,必须开启此参数或自行手写 padding。

没有伪共享防护的话:LongAdder 的吞吐会下降到 AtomicLong 的水平——这才是它真正性能爆杀的关键。

# 5.7 sum 方法的弱一致性

public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs) {
            if (c != null) sum += c.value;       // ★ 遍历期间 Cell 仍可能被写
        }
    }
    return sum;
}
1
2
3
4
5
6
7
8
9
10

警告:sum() 不是强一致快照——遍历过程中其他线程可能仍在修改 Cell。如果业务需要严格一致的快照(如对账),必须配合外部锁;但 95% 的统计场景(如 QPS、计数器、累计值)最终一致就够了。

sumThenReset():边读边重置,用于"周期性归零统计"——但同样不是原子的。

# 5.8 LongAccumulator/DoubleAdder 兄弟

类 用途 操作
LongAdder 求和 +x
LongAccumulator 任意二元运算 accum.apply(prev, x)
DoubleAdder double 求和 +x(精度受限)
DoubleAccumulator double 任意运算 同上

LongAccumulator 例子——并发求最大值:

LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);
max.accumulate(42);
max.accumulate(100);
max.accumulate(7);
long result = max.get();   // → 100
1
2
3
4
5

底层完全一样的 Striped64,只是把 + 换成自定义函数。注意:函数必须满足结合律 + 无副作用——因为 sum 顺序不确定。

# 6. Unsafe 时代的"危险但强大"

# 6.1 Unsafe 五大能力域

sun.misc.Unsafe(JDK 9+ 改名 jdk.internal.misc.Unsafe)是 JDK 内部"黑魔法"工具箱:

能力域 代表 API 用途
CAS compareAndSwapInt/Long/Object 原子原语
直接内存 allocateMemory / freeMemory 堆外内存(DirectByteBuffer)
线程调度 park / unpark LockSupport 底座
对象操作 objectFieldOffset / putXxx 直接读写字段
类操作 defineClass / allocateInstance 绕过构造方法

Unsafe 是 Java 并发体系的"最底层水源"——AQS 的 state CAS、Lock 的 park、Atomic 的 compareAndSwap、DirectByteBuffer 的内存分配、CompletableFuture 的状态机——全都建在 Unsafe 上。

# 6.2 compareAndSwapXxx:CAS 的本尊

// JDK 8 Unsafe 核心 API
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt   (Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong  (Object o, long offset, long expected, long x);
1
2
3
4

native 实现就是 §2.2 看到的 lock cmpxchgl。Java 层任意 CAS 调用最终都收敛到这里。

HotSpot intrinsic 优化:JIT 识别 Unsafe.compareAndSwapInt,直接替换为 CPU 指令而非函数调用——零调用开销。

# 6.3 park/unpark:Lock/AQS 的物理基础

public native void park(boolean isAbsolute, long time);    // 当前线程阻塞
public native void unpark(Object thread);                  // 唤醒指定线程
1
2

与 wait/notify 的根本区别:

维度 wait/notify park/unpark
必须持锁? 必须持 monitor 不需要
唤醒粒度 notify 不指定 / notifyAll 全唤醒 unpark 精确指定线程
顺序敏感? notify 在 wait 前 → 信号丢失 unpark 在 park 前 → 不丢失(凭证机制)

凭证(permit)机制:每个线程有一个二值信号量(0 或 1)。unpark 把 permit 设为 1(已经是 1 也不累加);park 检查 permit——是 1 就消耗后立即返回,是 0 就阻塞。这就是为什么 LockSupport 不会丢失信号。36 篇 AQS 把整套同步框架建在 park/unpark 上,根因就在此。

# 6.4 allocateMemory 与堆外内存

long addr = unsafe.allocateMemory(1024);    // 分配 1KB 堆外内存
unsafe.putLong(addr, 42L);
long v = unsafe.getLong(addr);
unsafe.freeMemory(addr);
1
2
3
4

用途:

  • DirectByteBuffer —— NIO 零拷贝(卷六 42-44 篇会展开)
  • Netty 的 PooledByteBufAllocator —— 堆外内存池
  • Apache Arrow / RocksDB 的 off-heap 数据结构

风险:手动分配,不释放就泄漏——而且不被 GC 管理,泄漏不报 OOM 报"native OOM"。这是 16 篇 OOM 八大现场之一。

# 6.5 Unsafe 的获取黑魔法

// ❌ 直接 Unsafe.getUnsafe() —— 系统类才有权限
Unsafe unsafe = Unsafe.getUnsafe();    // SecurityException

// ✅ 反射拿("破解"方式)
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
1
2
3
4
5
6
7

为什么 JDK 要禁止用户代码用 Unsafe:能力太强 —— 可以绕过 final、绕过类型检查、写任意内存——一个错误就崩 JVM。所以才要 VarHandle 取代它(§7)。

# 7. VarHandle 取代 Unsafe

# 7.1 JDK 9 模块化对 Unsafe 的封锁

JDK 9 模块化(JEP 261)后,sun.misc.Unsafe 被移到 jdk.unsupported 模块——默认不导出。代码里直接用会报:

warning: Unsafe is internal proprietary API and may be removed in a future release
1

JDK 17+ 进一步收紧——访问会触发 Illegal reflective access 警告,未来版本可能直接禁止。用户代码必须迁移到 VarHandle。

JDK 内部依然在用 Unsafe——AtomicXxx 在 JDK 8 是 Unsafe,JDK 9+ 改为 VarHandle,但 Unsafe 类本身仍保留以兼容遗留代码。

# 7.2 VarHandle 在 Atomic 中的落地

回看 §3.2 AtomicInteger 的源码:

private static final VarHandle VALUE;
static {
    MethodHandles.Lookup l = MethodHandles.lookup();
    VALUE = l.findVarHandle(AtomicInteger.class, "value", int.class);
}

public final boolean compareAndSet(int expectedValue, int newValue) {
    return VALUE.compareAndSet(this, expectedValue, newValue);
}
1
2
3
4
5
6
7
8
9

VarHandle 提供与 Unsafe 等价的能力,但:

  • 类型安全(编译期检查)
  • 受模块封装保护(不能跨模块访问私有字段)
  • 提供分级内存语义(§7.3)

# 7.3 与 31 篇的衔接:四级访问模式

31 篇 §5.2 已详细讲过 VarHandle 四级访问模式,这里只做与 CAS 的串联:

模式 性能 用法示例 何时选
plain 最快 vh.get(o) 单线程 / 已有外部同步
opaque 同上 vh.getOpaque(o) 仅需要原子但无内存可见性需求(如计数)
acquire/release 中 vh.getAcquire(o) / vh.setRelease(o, v) 类似 volatile 半屏障,单写多读场景
volatile 最慢 vh.getVolatile(o) / vh.compareAndSet(...) 完整 happens-before 语义

CAS 操作天然是 volatile 语义——compareAndSet 总是带完整内存屏障,不能降级。weakCompareAndSet 可以失败重试但不丢一致性。

# 7.4 Unsafe → VarHandle 迁移指南

// ❌ JDK 8 老代码
private static final long VALUE_OFFSET;
static {
    VALUE_OFFSET = unsafe.objectFieldOffset(MyClass.class.getDeclaredField("value"));
}
boolean ok = unsafe.compareAndSwapInt(this, VALUE_OFFSET, expect, update);

// ✅ JDK 9+ 等价代码
private static final VarHandle VALUE;
static {
    VALUE = MethodHandles.lookup().findVarHandle(MyClass.class, "value", int.class);
}
boolean ok = VALUE.compareAndSet(this, expect, update);
1
2
3
4
5
6
7
8
9
10
11
12
13

迁移收益:

  • 同样的字节码层零开销(VarHandle 也是 intrinsic)
  • 类型安全 + 模块友好
  • 4 级语义可选(opaque/acquire/release/volatile)

唯一缺点:VarHandle 必须 static final 才能享受 JIT 优化(31 篇 §8.3 讲过原因)——稍微多一点样板代码。

# 8. 性能实证

# 8.1 JMH 测试场景

环境:32 核 / 64 线程 / JDK 21 / 临界区 = 自增 1 次
对比:synchronized / AtomicLong / LongAdder / DoubleAdder
线程数:1 / 16 / 64
指标:吞吐 (M ops/s)
1
2
3
4

# 8.2 单线程基线

                    吞吐 (M ops/s)
synchronized            120
AtomicLong              280       ← 单线程 CAS 比锁快 2 倍多
LongAdder               250       ← 比 AtomicLong 略慢(多一次 cells null 检查)
1
2
3
4

关键观察:单线程下 LongAdder 反而比 AtomicLong 慢 —— 没竞争时分段累加只是浪费。

# 8.3 16 线程高竞争

                    吞吐 (M ops/s)
synchronized             8        ← 全部串行化
AtomicLong              45        ← 大量 CAS 失败重试
LongAdder              430        ← 接近线性扩展 ⚡
1
2
3
4

LongAdder 此时已经爆杀 AtomicLong 近 10 倍。AtomicLong 因为 cache line 抢夺,吞吐随线程数增加反而下降。

# 8.4 64 线程极端竞争

                    吞吐 (M ops/s)
synchronized             5
AtomicLong              28        ← 恶化
LongAdder             1250        ★★★ 仍线性扩展 ★★★
1
2
3
4

结论:竞争越激烈,LongAdder 优势越明显——这就是它存在的全部理由。

# 8.5 选型决策树

是否需要"原子操作"?
├── 否 → 普通变量 + volatile
│
└── 是
    │
    竞争程度?
    ├── 单线程 / 极低竞争(< 4 线程)
    │    └── AtomicLong / AtomicInteger(最简单)
    │
    ├── 中等竞争(4 ~ 16 线程)
    │    │
    │    需要精确读取吗?
    │    ├── 需要 → AtomicLong
    │    └── 最终一致即可 → LongAdder
    │
    └── 高竞争(> 16 线程)
         │
         一定用 LongAdder!
         │
         需要自定义运算?
         ├── 是 → LongAccumulator
         └── 否 → LongAdder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

额外维度:

  • 对象引用 → AtomicReference(防 ABA → AtomicStampedReference)
  • 数组元素 → AtomicXxxArray(注意伪共享)
  • 改造已有类字段 → AtomicXxxFieldUpdater
  • JDK 9+ 自定义无锁数据结构 → 直接用 VarHandle

# 9. 综合回扣与设计哲学

# 9.1 案例真相揭晓

① §1.1 AtomicLong 32 核翻车的真相:CAS 不是"零成本无锁"——LOCK CMPXCHG 触发 RFO + 全核 Invalidate 广播,竞争激烈时退化为"软件锁"。根治:换成 LongAdder,分段累加 + @Contended 防伪共享,吞吐恢复线性可扩展。

② §1.2 ABA 转账幽灵的真相:CAS 比的是地址相等(==),同一引用值的对象状态可能已"绕一圈"。根治:用 AtomicStampedReference,每次更新带版本戳,地址相同但戳不同时 CAS 失败重试。

③ 6 大追问全部作答:

追问 答案 章节
① CAS 凭什么原子 LOCK CMPXCHG / LL+SC 单 CPU 指令 §2.2-2.4
② Atomic 全家桶 基本/引用/数组/字段更新器四类,VarHandle 落地 §3
③ ABA 防护 StampedReference 版本戳 / MarkableReference 标志 §4
④ LongAdder 凭什么快 10 倍 Striped64 分段 + @Contended 伪共享防护 §5
⑤ 为什么 VarHandle 取代 Unsafe 模块化封装 + 类型安全 + 四级语义 §6.5 + §7.1
⑥ AtomicLong vs LongAdder 选型 低竞争 AtomicLong,高竞争必 LongAdder §8.5

# 9.2 三大设计哲学

1. "无锁三要素"——硬件原子 + 内存屏障 + 自旋重试:CAS 本身只是"原子比较交换",要构成完整的无锁算法需要三个支柱。

  • 硬件原子:CPU 指令保证读-比-写不可分割(LOCK CMPXCHG / LL+SC)
  • 内存屏障:保证 happens-before(volatile / acquire-release)
  • 自旋重试:失败后重新读 → 重新计算 → 重新 CAS

少了任何一个,无锁都不成立。这也是为什么 31 篇 VarHandle 提供 acquire/release 等分级语义——不同算法需要不同强度的屏障,强行用 volatile 是性能浪费,用 plain 又可能不正确。

2. "分段思想"——化整为零的并发万能药:LongAdder 把单一热点拆成 N 个分段;ConcurrentHashMap 把全表锁拆成段锁(JDK 7)/ Node 锁(JDK 8);ForkJoinPool 把全局队列拆成 per-worker 队列;ReadWriteLock 把锁拆成读写两半。所有"高吞吐"并发数据结构都在做同一件事——降低共享数据的争用粒度。

回扣链:

  • 卷一 09 篇 ConcurrentHashMap 分段
  • 卷二 22 篇 Stream 并行 + ForkJoinPool 工作窃取
  • 卷五 36 篇 AQS state 高低位编码(也是一种"分段")
  • 本篇 LongAdder Cell 数组
  • 卷六 47 篇 Reactor 多 EventLoop 模型(同思想)

3. "语义分级"——为正确性付出的代价应该按需:JDK 早期只有 volatile(最强语义),JDK 9 后通过 VarHandle 提供 plain/opaque/acquire-release/volatile 四级。强语义保证正确,但有性能代价;分级让用户在"够用就好"和"绝对正确"之间精确选择。这种思路:

  • 数据库的事务隔离级别(READ COMMITTED → SERIALIZABLE)
  • 缓存的一致性级别(最终一致 → 强一致)
  • TCP 的可靠传输 vs UDP 的尽力而为

架构师的价值就在于"识别每段代码需要的最低语义级别"——多了浪费,少了出 bug。

# 9.3 速查表与下一篇预告

API 速查:

基本类型                            引用类型
─ AtomicInteger                     ─ AtomicReference
─ AtomicLong                        ─ AtomicStampedReference  ← ABA 防护
─ AtomicBoolean                     ─ AtomicMarkableReference

数组                                字段更新器
─ AtomicIntegerArray                ─ AtomicIntegerFieldUpdater
─ AtomicLongArray                   ─ AtomicLongFieldUpdater
─ AtomicReferenceArray              ─ AtomicReferenceFieldUpdater

累加器(高竞争优化)                 底层
─ LongAdder       (求和)            ─ Unsafe (JDK 内部)
─ LongAccumulator (任意运算)        ─ VarHandle (用户首选)
─ DoubleAdder
─ DoubleAccumulator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

死规矩:

1. 单线程 / 低竞争用 AtomicLong;高竞争(≥ 16 线程)必换 LongAdder
2. 涉及对象池 / 状态可绕回的场景必须 AtomicStampedReference 防 ABA
3. AtomicXxxArray 多线程访问相邻索引时注意伪共享
4. JDK 9+ 用户代码禁止 sun.misc.Unsafe,必须 VarHandle
5. VarHandle 必须声明为 static final 才能享受 JIT intrinsic 优化
6. LongAdder.sum() 是弱一致,对账等强一致场景必须配外部锁
1
2
3
4
5
6

🎯 下一篇预告:第 39 篇《五大同步器对比:CountDownLatch / CyclicBarrier / Semaphore / Exchanger / Phaser》——本篇拿下了"无锁原语"层,36 篇打通了 AQS 骨架、37 篇讲完了三把锁,下一篇把 AQS 的"应用层旗舰产品"一次讲透:CountDownLatch 一次性闸门(state 倒数 → 0 全开)+ CyclicBarrier 可循环屏障(基于 ReentrantLock + Condition 而非 AQS state)+ Semaphore 信号量(公平/非公平 + tryAcquireShared 源码)+ Exchanger 双向交换站(slot + spin + park 的精妙撮合)+ Phaser 阶段同步器(树形分层 + 动态注册 + tieredPhaser 源码)。一篇把 5 个工具横向对比 + 源码贯通 + 选型决策树 + 5 大典型业务场景案例(压测发令枪 / 多源数据汇聚 / 限流降级 / 生产者消费者交换 / 多阶段批处理)讲完。AQS 的"五大旗舰应用"一次讲透。

上次更新: 2026/06/10, 11:13:41
并发锁三剑客
五大同步器对比

← 并发锁三剑客 五大同步器对比→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式