乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

原子类详解

飞翔科技性能优化作战室,凌晨两点。

小崔(初级开发,盯着火焰图):"奇怪,我用 synchronized 保护的那个全局请求计数器,CPU 占用怎么会这么高?每秒才 5 万 QPS 啊。"

白歌(首席架构师):"synchronized 在竞争激烈的时候会膨胀成重量级锁,频繁的上下文切换把你的 CPU 吃光了。JDK 8 的原子类用 CAS 无锁算法,性能能提升几十倍。"

Frank(后端专家,在白板上画 CPU 缓存图):"CAS — Compare-And-Swap — is a single atomic CPU instruction. No locks, no context switches. The CPU executes cmpxchg and you get atomicity at the hardware level."

大翔(CTO):"但是 CAS 也有 ABA 问题。朱璐上次压测就踩过这个坑——引用被改来改去,看起来值没变,实际对象早换了。"

朱璐(测试组长,委屈):"我当时就说了用 AtomicStampedReference 加个版本号就好了嘛..."

白歌:"那今晚我们就从 CAS 原理讲到 LongAdder,再到 ABA 问题的解决方案,把原子类彻底吃透。"


一、定义表

概念定义JDK 8 关键类
CASCompare-And-Swap:比较并交换,一条 CPU 原子指令,比较内存值与期望值,相等则更新sun.misc.Unsafe
AtomicInteger基于 CAS 的原子 int,提供 incrementAndGet 等无锁操作java.util.concurrent.atomic
AtomicLong基于 CAS 的原子 long(JDK 8 中 VM 层面优化了 64 位 CAS)java.util.concurrent.atomic
AtomicBoolean基于 CAS 的原子 boolean,内部用 int 实现java.util.concurrent.atomic
AtomicReference基于 CAS 的原子对象引用java.util.concurrent.atomic
LongAdderJDK 8 新增:分段累加器,高并发下性能远优于 AtomicLongjava.util.concurrent.atomic
Striped64LongAdder/DoubleAdder 的基类,实现分段 CAS(Cell 数组)java.util.concurrent.atomic
ABA 问题CAS 操作中值从 A→B→A,线程误以为值未变—
AtomicStampedReference带版本号(stamp)的原子引用,解决 ABA 问题java.util.concurrent.atomic
AtomicMarkableReference带布尔标记的原子引用,适用于"是否被修改过"场景java.util.concurrent.atomic
AtomicIntegerFieldUpdater基于反射的原子字段更新器,用于对已有类的 volatile int 字段进行 CASjava.util.concurrent.atomic

二、CAS 原理深度解析

2.1 CAS 循环流程图

2.2 CAS 的 CPU 层面实现

// Unsafe 类中的 CAS 方法(native)
public final native boolean compareAndSwapInt(
    Object o,       // 目标对象
    long offset,    // 字段内存偏移量
    int expected,   // 期望值
    int x           // 新值
);
// 对应 x86 CPU 指令: LOCK CMPXCHG

三、ABA 问题图解


四、LongAdder 分段累加原理(JDK 8 核心优化)

核心思想:Striped64 将热点分散到多个 Cell,每个线程 CAS 自己的 Cell,竞争降到 1/N。sum() 时累加所有 Cell,保证最终一致性(非强一致)。


五、代码示例

示例一:飞翔科技全局请求计数器——AtomicInteger vs synchronized 性能对比

场景:飞翔科技的 API 网关需要维护一个全局请求计数器,每秒数万次递增操作。对比 synchronized、AtomicInteger 和 LongAdder 三种方案的性能。

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;

/**
 * 飞翔科技 —— 全局请求计数器性能对比
 * synchronized vs AtomicInteger vs LongAdder
 */
public class CounterPerformanceComparison {

    // 方案1:synchronized
    static class SyncCounter {
        private long count = 0;
        public synchronized void increment() { count++; }
        public synchronized long get() { return count; }
    }

    // 方案2:AtomicInteger
    static class AtomicCounter {
        private final AtomicInteger count = new AtomicInteger(0);
        public void increment() { count.incrementAndGet(); }
        public int get() { return count.get(); }
    }

    // 方案3:LongAdder(JDK 8)
    static class AdderCounter {
        private final LongAdder count = new LongAdder();
        public void increment() { count.increment(); }
        public long get() { return count.sum(); }
    }

    /**
     * 执行压测
     */
    static long benchmark(Runnable task, int threadCount, int iterationsPerThread)
            throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            executor.execute(() -> {
                try {
                    startLatch.await();  // 等待发令
                    for (int j = 0; j < iterationsPerThread; j++) {
                        task.run();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    doneLatch.countDown();
                }
            });
        }

        long start = System.nanoTime();
        startLatch.countDown();  // 发令!
        doneLatch.await();
        long elapsed = System.nanoTime() - start;

        executor.shutdown();
        return elapsed;
    }

    public static void main(String[] args) throws Exception {
        final int THREADS = 10;
        final int ITERATIONS = 1_000_000;  // 每线程100万次

        System.out.println("===== 飞翔科技计数器性能对比测试 =====");
        System.out.printf("线程数: %d | 每线程操作数: %,d | 总操作数: %,d%n%n",
                THREADS, ITERATIONS, THREADS * ITERATIONS);

        // 预热 JIT
        SyncCounter warmUp = new SyncCounter();
        benchmark(warmUp::increment, 2, 1000);

        // 测试 synchronized
        SyncCounter syncCounter = new SyncCounter();
        long syncTime = benchmark(syncCounter::increment, THREADS, ITERATIONS);
        System.out.printf("synchronized:  %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
                syncCounter.get(),
                syncTime / 1_000_000,
                (THREADS * ITERATIONS) * 1_000_000_000.0 / syncTime);

        // 测试 AtomicInteger
        AtomicCounter atomicCounter = new AtomicCounter();
        long atomicTime = benchmark(atomicCounter::increment, THREADS, ITERATIONS);
        System.out.printf("AtomicInteger: %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
                atomicCounter.get(),
                atomicTime / 1_000_000,
                (THREADS * ITERATIONS) * 1_000_000_000.0 / atomicTime);

        // 测试 LongAdder
        AdderCounter adderCounter = new AdderCounter();
        long adderTime = benchmark(adderCounter::increment, THREADS, ITERATIONS);
        System.out.printf("LongAdder:     %,d ops | 耗时: %,d ms | TPS: %,.0f%n",
                adderCounter.get(),
                adderTime / 1_000_000,
                (THREADS * ITERATIONS) * 1_000_000_000.0 / adderTime);

        // 性能对比
        System.out.println("\n===== 性能倍数(以 synchronized 为基准) =====");
        System.out.printf("AtomicInteger 是 synchronized 的 %.1f 倍%n",
                (double) syncTime / atomicTime);
        System.out.printf("LongAdder 是 synchronized 的 %.1f 倍%n",
                (double) syncTime / adderTime);
        System.out.printf("LongAdder 是 AtomicInteger 的 %.1f 倍%n",
                (double) atomicTime / adderTime);
    }
}

控制台输出:

===== 飞翔科技计数器性能对比测试 =====
线程数: 10 | 每线程操作数: 1,000,000 | 总操作数: 10,000,000

synchronized:  10,000,000 ops | 耗时: 1,847 ms | TPS: 5,414,185
AtomicInteger: 10,000,000 ops | 耗时: 512 ms | TPS: 19,531,250
LongAdder:     10,000,000 ops | 耗时: 98 ms | TPS: 102,040,816

===== 性能倍数(以 synchronized 为基准) =====
AtomicInteger 是 synchronized 的 3.6 倍
LongAdder 是 synchronized 的 18.8 倍
LongAdder 是 AtomicInteger 的 5.2 倍

示例二:飞翔科技无锁栈——AtomicReference 实现线程安全的链表

场景:飞翔科技的实时日志系统使用无锁栈(Treiber Stack)来缓存待处理的日志条目,多个采集线程并发 push/pop,用 AtomicReference 实现无锁操作。

import java.util.concurrent.atomic.AtomicReference;

/**
 * 飞翔科技 —— 无锁日志栈(Treiber Stack)
 * 使用 AtomicReference 实现线程安全的单向链表栈
 */
public class LockFreeLogStack {

    // 日志节点
    static class LogNode {
        final String logEntry;
        final long timestamp;
        LogNode next;

        LogNode(String logEntry) {
            this.logEntry = logEntry;
            this.timestamp = System.currentTimeMillis();
        }

        @Override
        public String toString() {
            return String.format("[%tT] %s", timestamp, logEntry);
        }
    }

    // 无锁栈
    static class TreiberStack {
        // 栈顶指针 —— AtomicReference 保证原子性
        private final AtomicReference<LogNode> top =
                new AtomicReference<>(null);

        /**
         * 入栈(CAS 循环)
         */
        public void push(String logEntry) {
            LogNode newNode = new LogNode(logEntry);
            LogNode oldTop;
            do {
                oldTop = top.get();          // 读取当前栈顶
                newNode.next = oldTop;       // 新节点指向旧栈顶
                // CAS: 期望栈顶仍是 oldTop,则更新为 newNode
            } while (!top.compareAndSet(oldTop, newNode));
        }

        /**
         * 出栈(CAS 循环)
         */
        public LogNode pop() {
            LogNode oldTop;
            LogNode newTop;
            do {
                oldTop = top.get();
                if (oldTop == null) {
                    return null;             // 栈空
                }
                newTop = oldTop.next;        // 新栈顶 = 旧栈顶的 next
            } while (!top.compareAndSet(oldTop, newTop));
            return oldTop;
        }

        /**
         * 查看栈顶(不弹出)
         */
        public LogNode peek() {
            return top.get();
        }

        public int size() {
            int count = 0;
            LogNode node = top.get();
            while (node != null) {
                count++;
                node = node.next;
            }
            return count;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TreiberStack logStack = new TreiberStack();

        System.out.println("===== 飞翔科技实时日志收集系统 =====");
        System.out.println("使用无锁栈 (Treiber Stack) 缓存日志条目\n");

        // 模拟 5 个采集线程并发 push 日志
        Thread[] producers = new Thread[5];
        for (int t = 0; t < 5; t++) {
            final int threadId = t;
            producers[t] = new Thread(() -> {
                for (int i = 0; i < 20; i++) {
                    String log = String.format("采集器-%d | 日志条目 #%d", threadId, i);
                    logStack.push(log);
                    // 模拟采集间隔
                    try {
                        Thread.sleep((long)(Math.random() * 5));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }, "collector-" + t);
            producers[t].start();
        }

        // 等待所有采集完成
        for (Thread t : producers) {
            t.join();
        }

        System.out.printf("日志栈中总条目数: %d%n", logStack.size());

        // 消费日志(pop 所有条目)
        System.out.println("\n===== 弹出所有日志(最新优先) =====");
        int count = 0;
        LogNode node;
        while ((node = logStack.pop()) != null) {
            System.out.printf("[%d] %s%n", ++count, node);
        }

        // 验证栈已空
        System.out.printf("%n栈已清空,剩余: %d 条%n", logStack.size());
        System.out.println("无锁栈操作完成!");
    }
}

控制台输出(部分):

===== 飞翔科技实时日志收集系统 =====
使用无锁栈 (Treiber Stack) 缓存日志条目

日志栈中总条目数: 100

===== 弹出所有日志(最新优先) =====
[1] [14:32:05] 采集器-4 | 日志条目 #19
[2] [14:32:05] 采集器-3 | 日志条目 #19
[3] [14:32:05] 采集器-2 | 日志条目 #19
...
[99] [14:32:00] 采集器-0 | 日志条目 #1
[100] [14:32:00] 采集器-1 | 日志条目 #0

栈已清空,剩余: 0 条
无锁栈操作完成!

六、AtomicStampedReference 解决 ABA 问题

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 飞翔科技 —— AtomicStampedReference 解决 ABA 问题
 * 
 * 场景:链表节点回收复用,用版本号(stamp)标识是否被修改过
 */
public class ABAProblemSolution {

    // 使用带版本号的原子引用
    static class SafeStack<T> {
        static class Node<T> {
            final T value;
            Node<T> next;
            Node(T value) { this.value = value; }
        }

        // AtomicStampedReference: 引用 + 版本号
        private final AtomicStampedReference<Node<T>> top =
                new AtomicStampedReference<>(null, 0);

        public void push(T value) {
            Node<T> newNode = new Node<>(value);
            int[] stampHolder = new int[1];
            Node<T> oldTop;
            do {
                oldTop = top.get(stampHolder);   // 获取当前栈顶和版本号
                int oldStamp = stampHolder[0];
                newNode.next = oldTop;
                // CAS: 比较引用和版本号,都匹配才更新,版本号+1
                if (top.compareAndSet(oldTop, newNode, oldStamp, oldStamp + 1)) {
                    return;  // 成功
                }
                // 失败则重试
            } while (true);
        }

        public T pop() {
            int[] stampHolder = new int[1];
            Node<T> oldTop;
            Node<T> newTop;
            do {
                oldTop = top.get(stampHolder);
                if (oldTop == null) return null;
                int oldStamp = stampHolder[0];
                newTop = oldTop.next;
                if (top.compareAndSet(oldTop, newTop, oldStamp, oldStamp + 1)) {
                    return oldTop.value;
                }
            } while (true);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SafeStack<String> safeStack = new SafeStack<>();
        
        System.out.println("===== ABA 问题复现与解决 =====");
        
        // 线程1: 执行慢操作
        Thread t1 = new Thread(() -> {
            safeStack.push("A");
            safeStack.push("B");
            safeStack.push("C");
            System.out.println("[线程1] 压入: C, B, A");
            
            // 准备 pop "C"(但中间会被打断)
            System.out.println("[线程1] 开始 pop C...");
            // 使用 AtomicStampedReference 后,即使被ABA干扰也能检测到
            String val = safeStack.pop();
            System.out.println("[线程1] pop 结果: " + val +
                    " (版本号机制保证正确性)");
        }, "Thread-1");

        Thread t2 = new Thread(() -> {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            // 干扰线程:快速 pop 再 push
            String v1 = safeStack.pop();  // pop C
            String v2 = safeStack.pop();  // pop B
            System.out.println("[线程2] 干扰操作: pop了 " + v1 + " 和 " + v2);
            safeStack.push("X");
            System.out.println("[线程2] push X (制造ABA场景)");
        }, "Thread-2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("\n结论: AtomicStampedReference 通过版本号避免了 ABA 问题。");
    }
}

七、易错场景

7.1 误用 AtomicInteger 做复合操作

// ❌ 错误:get() + compareAndSet() 不是原子操作!
AtomicInteger balance = new AtomicInteger(100);

// 线程A: 检查余额
int current = balance.get();          // 读到 100
// 线程B 此时扣款 80 → balance = 20
if (current >= 80) {
    balance.compareAndSet(current, current - 80);  // CAS 失败!
    // 正确做法:直接用 getAndAdd / accumulateAndGet
}

正确做法:使用 balance.getAndUpdate(x -> x >= 80 ? x - 80 : x) 或 updateAndGet()(JDK 8)。

7.2 LongAdder 的 sum() 不是强一致的

// ❌ 错误认知:以为 sum() 返回的是精确的瞬时值
LongAdder adder = new LongAdder();
// 10个线程并发 increment
// 调用 sum() 时,可能有 Cell 中的值在变化
long val = adder.sum();  // sum() 不是强一致的!返回近似值

正确理解:LongAdder.sum() 遍历所有 Cell 累加,遍历期间其他线程可能修改 Cell,所以返回的是"近似值"。如果需要精确值,请在并发操作停止后调用,或使用 sumThenReset()。

7.3 忘记 AtomicReference 比较的是引用而非内容

// ❌ 陷阱:AtomicReference 用 == 比较,不是 equals()!
AtomicReference<String> ref = new AtomicReference<>(new String("hello"));
boolean result = ref.compareAndSet(new String("hello"), "world");
System.out.println(result);  // false!因为是不同对象

正确理解:compareAndSet 使用 == 比较引用,不是 equals()。对于值类型(String、Integer 等),务必持有同一个对象引用。

7.4 AtomicIntegerFieldUpdater 字段必须为 volatile

// ❌ 错误:字段不是 volatile 会抛异常
class Counter {
    int count = 0;  // 缺少 volatile!
}
AtomicIntegerFieldUpdater<Counter> updater =
    AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
// 运行时: java.lang.IllegalArgumentException: Must be volatile type

正确做法:目标字段必须声明为 volatile int,且不能是 private(除非调用者有访问权限)。


八、面试考点

Q1:CAS 的 ABA 问题是什么?如何解决?(高频)

答:

  • ABA 问题:CAS 比较的只是值,如果值经历了 A→B→A 的变化,CAS 会误判为"值未改变",从而执行交换。在引用类型场景下(如链表节点),节点可能已被删除回收,造成悬空指针。
  • 解决方案:
    • AtomicStampedReference<V>:维护一个 int 版本号(stamp),每次更新 stamp+1,同时比较引用和版本号。
    • AtomicMarkableReference<V>:维护一个 boolean 标记,适合"是否被修改过"的简单场景。
    • 关键:JDK 8 中 compareAndSet(V expectedRef, V newRef, int expectedStamp, int newStamp) 是原子操作。

Q2:AtomicLong 和 LongAdder 的区别?什么场景用哪个?(高频)

答:

AtomicLongLongAdder (JDK 8)
单值 CAS,高并发时大量自旋分段 Cell 数组,分散热点
incrementAndGet() 立即返回精确值increment() 不返回值,sum() 返回近似值
低~中并发下性能好高并发下性能远超 AtomicLong
内存占用小内存占用大(Cell 数组)
需要精确值的场景(如序列号生成)统计计数场景(如 QPS 统计)

核心原理:LongAdder 继承 Striped64,将竞争分散到多个 Cell(通过 Thread.probe 哈希映射),需要 sum 时累加 base + 所有 Cell。

Q3:AtomicReference 的 compareAndSet 用的是 == 还是 equals?

答:compareAndSet 底层调用 Unsafe.compareAndSwapObject,使用的是 引用相等(==),而非 equals()。这是因为 CAS 在 CPU 层面比较的是内存地址。如果需要基于 equals() 的原子更新,需要自行配合 compareAndSet 循环实现。

Q4:JDK 8 中 AtomicInteger 新增了哪些方法?

答:JDK 8 为所有原子类新增了函数式更新方法:

  • getAndUpdate(IntUnaryOperator) — 原子地更新并返回旧值。
  • updateAndGet(IntUnaryOperator) — 原子地更新并返回新值。
  • getAndAccumulate(int x, IntBinaryOperator) — 原子地累加并返回旧值。
  • accumulateAndGet(int x, IntBinaryOperator) — 原子地累加并返回新值。
AtomicInteger ai = new AtomicInteger(5);
ai.updateAndGet(x -> x * 3);  // 15
ai.getAndAccumulate(10, (a, b) -> a + b); // 返回15,然后变成25

这些方法使原子类可以配合 Lambda 表达式使用,极大简化了复合 CAS 操作。


九、原子类选型速查表

场景推荐
计数器,低竞争AtomicInteger / AtomicLong
计数器,高竞争(QPS统计)LongAdder
布尔标志位AtomicBoolean
对象引用更新AtomicReference<V>
需要解决 ABA 问题AtomicStampedReference<V>
只需标记"是否被修改"AtomicMarkableReference<V>
对已有类的 volatile 字段做 CASAtomicIntegerFieldUpdater<T>
数组元素原子操作AtomicIntegerArray / AtomicLongArray
复合 CAS 操作updateAndGet / accumulateAndGet(JDK 8 Lambda)

上一篇:并发工具类详解  |  下一篇:ThreadLocal详解

上一页
ThreadLocal详解
下一页
并发工具类详解