Java并发包atomic原子操作类

前置总述

原子操作类是JUC(java.util.concurrent)提供的无锁化线程安全工具 ,包路径为java.util.concurrent.atomic,核心是基于CAS+Unsafe+volatile实现单个变量 的原子操作。

它的核心价值是替代传统的synchronized/Lock ,做到更轻量、无线程上下文切换开销,是高并发开发中处理单个变量线程安全的首选方案。
核心定位:细粒度、无锁、非阻塞的单个变量原子更新工具,无法替代锁解决多变量/代码块的原子性问题。


一、基础概念(面试开篇必问)

1. 原子操作的定义

原子操作 :指不可分割的操作 ,一个操作要么全部执行成功 ,要么全部不执行 ,不存在任何中间状态。

可以通俗理解为:操作是"一次性"的,不会被线程调度器打断,多线程下不会出现"操作做了一半"的情况。

2. 为什么需要原子操作类?------ 解决非原子操作的线程安全问题

Java中很多看似"简单"的操作其实是非原子的,最典型的就是**i++/count+=1**,这也是面试最常考的反例。

i++的本质:3个独立步骤(读→改→写)

  1. :从主存中读取变量i的当前值,加载到线程的工作内存;
  2. :在线程工作内存中对i执行+1操作;
  3. :将修改后的值写回主存。

多线程问题 :这3步是独立的,线程调度器可能在任意一步打断线程,导致中间值覆盖 ,最终计数丢失。

举个例子:线程A和B同时对i=1执行i++

  • 线程A读i=1,还没改,被打断;
  • 线程B读i=1,完成+1,写回主存i=2
  • 线程A恢复执行,基于之前读的1+1成2,写回主存i=2
  • 结果:两个线程各执行一次i++,最终i=2而非预期的3,丢了一次计数。

原子操作类的核心作用,就是把这类"读-改-写"的非原子操作,封装成原子操作,避免多线程下的数值丢失。

3. Java实现原子操作的3种方式

Java中实现原子操作有3种核心方式,对应不同的并发场景,面试常考三者的区别:

实现方式 核心代表 核心思想(通俗说) 特点
悲观锁 synchronized、ReentrantLock 认为一定会有线程竞争,先"占坑"(加锁),独占资源,其他线程阻塞等待 阻塞式、粗粒度,开销大(上下文切换)
乐观锁 atomic原子操作类 认为很少有线程竞争,不加锁,直接尝试更新,失败则重试 非阻塞式、细粒度,开销小(无上下文切换)
底层CPU指令 x86的cmpxchg指令 硬件层面直接保证操作的原子性,是前两者的底层支撑 最底层,JVM和Java类库基于此实现

核心关联:原子操作类的乐观锁(CAS),最终就是通过调用CPU的CAS硬件指令实现的,是"硬件层面的乐观锁"。

4. 原子操作类与锁(synchronized/Lock)的核心区别

面试必问的核心对比,一句话总结:原子类是无锁非阻塞的细粒度操作,锁是有锁阻塞的粗粒度操作,详细区别如下:

  1. 锁机制 :原子类无锁 (基于CAS自旋),锁有锁(独占锁,悲观锁);
  2. 线程状态 :原子类非阻塞 (线程失败时自旋重试,不进入阻塞状态),锁阻塞(线程失败时进入WAITING/BLOCKED,等待唤醒);
  3. 操作粒度 :原子类仅保证单个变量 的原子性,锁保证代码块/方法的原子性(多变量、多操作均可);
  4. 性能开销 :原子类无上下文切换/调度开销,轻量高效;锁存在上下文切换、唤醒等开销,重量;
  5. 适用场景 :原子类适用于单个变量的简单操作 (计数、状态标记),锁适用于多变量/复杂逻辑的原子性保证。

二、原子操作类的核心分类(按功能划分,核心使用)

原子操作类按操作数据类型+功能扩展 分为5大类,前4类JDK5新增,第5类是JDK8新增的高频核心类(大厂面试重点),其中标★的是面试最常考察、开发最常用的类。

整体分类速查表(建议熟记,面试开篇题常考):

分类 核心代表类 适用场景 核心注意点
基本类型原子类★ AtomicInteger、AtomicLong、AtomicBoolean 单个基本类型(int/long/boolean)原子更新 AtomicBoolean底层用int实现(Unsafe仅支持数值CAS)
数组类型原子类 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 数组中单个元素的原子更新 仅操作数组下标,非整个数组原子
引用类型原子类★ AtomicReference、AtomicStampedReference★、AtomicMarkableReference★ 单个对象引用的原子更新 专门解决CAS的ABA问题(面试核心)
对象字段更新原子类★ AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 普通对象的非静态字段原子更新 字段必须满足:volatile+非private+非static
JDK8原子累加器★ LongAdder、DoubleAdder、LongAccumulator★、DoubleAccumulator★ 高并发下的累加/计数场景 替代AtomicLong,解决高并发自旋瓶颈

子模块2.1 基本类型原子类(最基础、最常用)

核心是AtomicIntegerAtomicLongAtomicBoolean,分别对应int、long、boolean类型的原子更新,三者用法高度一致,以AtomicInteger为核心学习即可,面试考察也以它为主。

核心特点

  1. 内部核心字段valuevolatile修饰(保证可见性和有序性);
  2. 所有原子方法最终都通过Unsafe的native方法实现(如compareAndSwapInt);
  3. AtomicBoolean底层用int实现 (0表示false,1表示true),因为Unsafe类仅支持数值类型的CAS操作,无boolean类型的CAS方法(面试常考小细节)。

核心方法(AtomicInteger为例,熟记,开发/面试都常用)

方法名和功能一一对应,部分方法可以和普通的自增/累加操作类比,更好记:

方法名 功能说明 普通操作类比 面试注意点
get() 获取当前值 - volatile保证立即可见
set(int newValue) 设置新值 - 立即生效,volatile保证可见性
getAndSet(int newValue) 原子更新为新值,返回旧值 - 全原子操作
getAndIncrement() 原子执行i++,返回旧值 i++ 开发最常用的计数方法
incrementAndGet() 原子执行++i,返回新值 ++i 与上一个方法的区别是面试常考题
getAndAdd(int delta) 原子累加delta,返回旧值 count += delta 任意数值的原子累加
addAndGet(int delta) 原子累加delta,返回新值 - -
compareAndSet(int expect, int update) 核心CAS方法:预期值==实际值则更新,返回是否成功 - 原子类的底层核心,所有原子方法都基于它实现
lazySet(int newValue) 延迟设置新值,不保证立即可见 - 性能更高,适用于无需严格可见性的场景

方法使用小示例

java 复制代码
AtomicInteger ai = new AtomicInteger(0);
ai.incrementAndGet(); // 0→1,返回1(++i)
ai.getAndIncrement(); // 1→2,返回1(i++)
ai.getAndAdd(5); // 2→7,返回2
ai.compareAndSet(7, 10); // 预期7,更新为10,返回true
ai.lazySet(20); // 延迟设置为20,不保证立即可见

底层核心(面试常考)

getAndIncrement()为例,它的底层实现是循环CAS(自旋锁),伪代码如下:

java 复制代码
public final int getAndIncrement() {
    // 循环调用CAS,直到更新成功(自旋)
    while (true) {
        int current = get(); // 获取当前值(volatile保证最新)
        int next = current + 1; // 计算新值
        // CAS核心:预期值current,更新为next,成功则返回current
        if (compareAndSet(current, next)) {
            return current;
        }
    }
}

核心思想 :失败就重试,直到CAS成功,这也是自旋锁的典型实现(后续会详细讲)。

子模块2.2 数组类型原子类

核心是AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray,对应数组的原子更新,开发中使用频率一般,面试考察较少,核心掌握"操作粒度"即可。

核心特点

  1. 仅保证数组中 单个元素的原子更新,而非整个数组的原子操作(面试易错点);
  2. 所有方法都基于数组下标操作,和普通数组的使用方式类似;
  3. 底层同样是CAS+Unsafe实现,数组的核心元素被volatile修饰。

核心使用示例

java 复制代码
// 初始化数组,初始值[1,2,3]
AtomicIntegerArray arr = new AtomicIntegerArray(new int[]{1,2,3});
arr.getAndIncrement(0); // 下标0的元素原子+1,1→2,返回1
arr.compareAndSet(1, 2, 5); // 下标1,预期2,更新为5,返回true
arr.get(2); // 获取下标2的元素,值为3

核心注意 :如果直接用new int[]创建普通数组,多线程下修改数组元素会有线程安全问题,数组原子类就是解决这个问题的。

子模块2.3 引用类型原子类★(面试核心,解决ABA问题)

基本类型原子类操作的是"数值",而引用类型原子类操作的是对象的引用地址,核心解决两个问题:

  1. 单个对象引用的原子更新(如User对象的引用);
  2. CAS的天然缺陷------ABA问题(面试必问,大厂高频考点)。

该类包含3个核心类,其中AtomicStampedReferenceAtomicMarkableReference是专门解决ABA问题的,是面试考察的重点。

2.3.1 基础类:AtomicReference

操作对象引用 的原子更新,用法和AtomicInteger高度一致,只是把"数值"换成了"对象引用"。
适用场景:多线程下原子更新一个对象的引用,比如多线程下修改一个配置对象的引用。

核心使用示例

java 复制代码
// 定义一个普通对象
class User {
    private String name;
    private int age;
    // 构造器、get/set省略
}

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        // 初始化AtomicReference,引用一个User对象
        AtomicReference<User> ar = new AtomicReference<>(new User("张三", 20));
        // 获取当前引用的对象
        User oldUser = ar.get();
        // 新建一个User对象
        User newUser = new User("李四", 25);
        // CAS原子更新引用:预期oldUser,更新为newUser
        boolean success = ar.compareAndSet(oldUser, newUser);
        System.out.println(success); // true
        System.out.println(ar.get().getName()); // 李四
    }
}

2.3.2 CAS的致命缺陷:ABA问题(面试必问,核心中的核心)

这是引用类型原子类的核心考点,必须理解定义、危害、解决方案,缺一不可。

1. ABA问题的通俗定义

线程1准备对变量V执行CAS操作,预期值为A ;在线程1执行CAS之前,线程2先将V从A→B→A(先改B再改回A);线程1执行CAS时,发现V的值还是A,误以为V从未被修改过,于是成功执行CAS,导致数据一致性问题。

可以用一个生活例子理解:

你把100块钱放在桌子上,出去接电话,同桌先把100块拿走,又放回来100块,你回来后看到桌子上还是100块,以为没人动过,但其实钱已经被拿走过了------这就是典型的ABA问题。

2. ABA问题的危害

简单计数场景 :ABA问题无实际危害 ,比如只是简单的i++,即使出现ABA,最终的数值结果还是正确的;
带状态的对象/复杂结构场景 :ABA问题会导致逻辑错误 ,这是最危险的,比如链表的原子入队/出队操作对象的状态更新

链表场景的ABA问题示例 (大厂面试常考):

链表节点:A→B→C,线程1准备将A节点出队(CAS将头节点从A改为B);线程2先将A出队,再将A重新入队,链表变回A→B→C;线程1执行CAS时,头节点还是A,误以为没动,成功执行CAS,但此时链表的内部状态已经被修改,可能导致链表遍历异常、节点丢失等问题。

3. ABA问题的核心解决方案

为变量绑定一个「版本号/标记位」 ,让CAS操作从仅对比"值" 变为同时对比"值+版本号/标记位"

只要变量被修改过,不管最终值是否变回原值,版本号/标记位都会发生变化,CAS时因为版本号不匹配,就会执行失败,从而解决ABA问题。

2.3.3 解决ABA问题的两个核心类(面试必背区别)

基于"值+版本号/标记位"的解决方案,JUC提供了两个类,分别对应不同的场景,面试常考两者的区别和适用场景

1. AtomicStampedReference★(全量解决ABA问题,大厂面试重点)

核心 :为变量绑定一个int类型的版本戳(stamp)每次更新变量时,版本戳必须+1 ,CAS时需要同时对比「对象引用+版本戳」,两者都匹配才能更新成功。
适用场景 :需要记录变量修改次数 的场景,比如链表、带状态的对象更新,是解决ABA问题的首选

核心方法(面试必记):

java 复制代码
// 构造器:初始值 + 初始版本戳
AtomicStampedReference(V initialRef, int initialStamp)
// 获取当前的「值+版本戳」,通过数组返回(Java无法返回多个值)
get(int[] stampHolder)
// 核心CAS方法:预期值、新值、预期版本戳、新版本戳,全匹配才更新
compareAndSet(V expectedRef, V newRef, int expectedStamp, int newStamp)
// 获取当前版本戳
int getStamp()
// 获取当前值
V getReference()

手写演示ABA问题并解决 (面试手写题高频,必须会写):

见本文末尾的「核心手写题示例2」,已附带详细注释和输出结果。

2. AtomicMarkableReference(半解决ABA问题,面试考区别)

核心 :为变量绑定一个boolean类型的标记位(mark) ,仅表示变量**"是否被修改过"(true=被修改过,false=未被修改过),CAS时同时对比「对象引用+标记位」。
适用场景:只需
判断变量是否被修改过**,无需记录修改次数的场景,比如简单的对象状态判断。

核心区别(面试必背):

  • AtomicStampedReference:版本戳是int类型,可递增 ,能记录变量的修改次数全量解决ABA问题
  • AtomicMarkableReference:标记位是boolean类型,仅true/false ,只能判断是否被修改过半解决ABA问题(若变量被修改多次,标记位可能变回原值)。

子模块2.4 对象字段更新原子类★(灵活更新,面试考使用条件)

核心是AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater,简称字段更新器

核心作用(为什么需要?)

在开发中,如果你想让一个普通对象的某个字段 支持原子更新,不需要为这个字段专门创建一个原子类(比如为User的age字段创建AtomicInteger),而是可以通过字段更新器 ,基于反射 实现普通对象字段的原子更新,节省内存空间,让代码更灵活。

简单说:用原子方式更新普通对象的非静态字段,无需修改原对象的代码。

强制使用条件★(面试必问,高频易错点)

字段更新器有4个强制使用条件 ,只要违反其中一个,就会抛出异常(如IllegalArgumentException),必须熟记并理解每个条件的原因

  1. 目标字段必须被volatile修饰:保证字段的可见性和有序性,否则CAS读取的是旧值,原子更新无意义(面试核心原因);
  2. 目标字段不能是private :字段更新器基于反射实现,反射无法获取私有的字段;
  3. 目标字段不能是static :字段更新器是基于对象实例的,静态字段属于类,而非单个实例,无法通过实例更新;
  4. 必须通过newUpdater()方法创建更新器:不能直接new,该方法通过泛型保证类型安全,参数为「对象的Class」+「字段名」。

核心使用示例

java 复制代码
// 普通对象:age字段满足volatile+非private+非static
class User {
    public volatile String name;
    public volatile int age; // 核心字段,满足所有条件
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class FieldUpdaterDemo {
    public static void main(String[] args) {
        // 1. 创建字段更新器:User类的age字段(int类型)
        AtomicIntegerFieldUpdater<User> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
        // 2. 创建普通User对象
        User user = new User("张三", 20);
        // 3. 原子更新age字段:+1
        ageUpdater.getAndIncrement(user);
        System.out.println(user.age); // 21
        // 4. CAS更新age字段:预期21,更新为25
        boolean success = ageUpdater.compareAndSet(user, 21, 25);
        System.out.println(success); // true
        System.out.println(user.age); // 25
    }
}

子模块2.5 JDK8原子累加器★(高并发核心,大厂面试高频对比)

核心是LongAdderDoubleAdderLongAccumulatorDoubleAccumulator,是JDK8为解决高并发计数瓶颈新增的核心类面试常考与AtomicLong的对比,开发中高并发计数场景几乎全用它替代AtomicLong。

2.5.1 为什么需要原子累加器?------ AtomicLong的高并发瓶颈

AtomicLong的底层是单变量CAS自旋 ,在低并发场景 下,自旋次数少,性能很高;但在高并发场景 下,大量线程同时竞争同一个变量的CAS,导致大部分线程都在自旋重试 ,CPU占用率飙升,性能急剧下降------这就是AtomicLong的自旋瓶颈

用生活例子理解:

AtomicLong相当于一个窗口打饭 ,所有同学都排队挤这个窗口,人多的时候排队时间极长;而LongAdder相当于多个窗口打饭,同学分散到不同窗口,排队时间大幅缩短,效率大幅提升。

2.5.2 核心类1:LongAdder/DoubleAdder(高并发累加首选)

LongAdder对应long类型,DoubleAdder对应double类型,以LongAdder为核心学习 ,是开发中高并发计数/累加的首选(如接口访问量、订单数统计)。

核心原理:分段CAS(Striped64)

LongAdder的底层抛弃了"单变量CAS",改为分段CAS(Striped64算法),内部维护两个核心变量:

  1. base变量:低并发场景下,直接CAS更新base变量(和AtomicLong一致),避免数组开销;
  2. cell数组 :高并发场景下,将竞争分散到cell数组的不同元素 ,每个线程通过哈希算法对应一个cell元素,线程只CAS更新自己的cell元素,避免单变量竞争

最终计数结果sum() = base + 所有cell数组元素的值,通过汇总base和cell数组得到最终结果。

核心方法
java 复制代码
add(long x) // 原子累加x(最常用,如add(1)就是计数+1)
increment() // 原子+1,等价于add(1)
decrement() // 原子-1,等价于add(-1)
sum() // 汇总base+cell,返回最终计数
reset() // 重置base和cell为0,用于重复计数
sumThenReset() // 先sum()再reset(),原子操作
核心注意点★(面试必问,高频易错点)
  1. sum()方法是最终一致性,非实时一致性 :汇总base+cell时,没有加锁,可能有线程正在修改cell数组,导致sum()的结果不是"实时最新值",存在微小延迟(面试核心原因);
  2. cell数组是懒加载的:初始时cell数组为空,只有当高并发竞争base变量时,才会初始化cell数组(节省内存);
  3. DoubleAdder底层用long存储 :因为Unsafe类不支持double类型的CAS操作,DoubleAdder通过Double.doubleToLongBits()将double转为long,CAS更新后再通过Double.longBitsToDouble()转回double(面试小细节);
  4. 适用场景 :高并发下的统计/计数场景 (如接口访问量、页面点击量),无需实时精确计数的场景;金融场景(如交易金额统计)禁止使用,因为需要实时精确计数。

2.5.3 核心类2:LongAccumulator/DoubleAccumulator(自定义累加规则)

是LongAdder/DoubleAdder的超集 ,不仅支持简单的"加法",还可以自定义累加规则(如乘法、求最大值、最小值),底层原理和LongAdder一致,也是分段CAS。

核心特点

通过**BinaryOperator函数式接口**自定义累加规则,构造器需要传入两个参数:

  1. 累加规则(BinaryOperator);
  2. 初始值。
核心使用示例(高并发求最大值,面试常考)
java 复制代码
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.BinaryOperator;

public class LongAccumulatorDemo {
    public static void main(String[] args) {
        // 自定义累加规则:求两个数的最大值,初始值0
        BinaryOperator<Long> maxFunc = (x, y) -> Math.max(x, y);
        LongAccumulator la = new LongAccumulator(maxFunc, 0L);

        // 多线程下更新,求最大值
        new Thread(() -> la.accumulate(10)).start();
        new Thread(() -> la.accumulate(20)).start();
        new Thread(() -> la.accumulate(15)).start();

        // 休眠等待线程执行完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(la.get()); // 20(高并发下原子求最大值)
    }
}

2.5.4 LongAdder vs AtomicLong★(面试必背对比,大厂高频)

这是JDK8原子类的核心面试题 ,必须熟记适用场景性能差异原因

特性 AtomicLong LongAdder
底层原理 单变量CAS自旋 分段CAS(base+cell数组)
高并发性能 差(自旋瓶颈,CPU占用高) 好(分散竞争,自旋次数少)
计数一致性 实时一致性(CAS单变量,精确) 最终一致性(sum()汇总无锁,有延迟)
方法支持 丰富(getAndAdd、compareAndSet等) 简单(add、increment、sum等)
内存开销 小(仅一个volatile long) 稍大(base+cell数组,懒加载)
适用场景 1. 低并发计数;2. 需实时精确计数的场景(如金融);3. 需要CAS高级方法的场景 1. 高并发统计/计数场景(如接口访问量);2. 无需实时精确计数的场景;3. 简单累加场景
性能优势 低并发下略优于LongAdder(无数组开销) 高并发下性能是AtomicLong的10倍以上

三、原子操作类的核心实现原理★(面试重中之重,底层必问)

原子操作类的底层是CAS + Unsafe + volatile 的组合,三者缺一不可 ,共同支撑原子操作类的线程安全,这是大厂面试必问的底层原理,必须理解三者的作用和关联关系。

一句话总结三者的核心分工:
volatile保证可见性和有序性(让线程能看到最新值),CAS保证原子性(让线程改值时不冲突),Unsafe提供底层实现(直接操作内存和CPU指令)

子模块3.1 CAS(Compare and Swap,比较并交换)★

CAS是乐观锁的核心算法 ,也是原子操作类的底层核心 ,所有原子操作最终都通过CAS实现,面试必问定义、执行流程、优缺点

1. CAS的通俗定义

CAS包含3个操作数,是一个"原子的比较并交换"操作:

  • V :要更新的变量的内存地址(通过Unsafe获取);
  • A :线程读取到的变量的旧的预期值
  • B :线程计算出的变量的新值

执行逻辑 (必须熟记):
当且仅当内存地址V中的实际值 == 预期值A时 ,将V中的值原子更新 为B;否则不做任何操作 ,返回false。线程可以选择自旋重试 (循环CAS)或放弃更新

2. CAS的执行流程(从Java到硬件,层层拆解)

CAS的执行是从上层Java到底层硬件的层层调用,面试常考这个流程:

  1. Java层 :原子操作类调用Unsafe的CAS方法(如compareAndSwapInt);
  2. JVM层 :Unsafe的CAS方法是native方法,JVM将其解析为底层的C/C++代码;
  3. 硬件层 :C/C++代码调用CPU的CAS硬件指令 (如x86架构的cmpxchg指令),这是CAS的最终实现

3. 多核CPU下的CAS原子性保证(面试常考)

CAS指令在单核CPU 下天然原子(无多核竞争,指令执行不会被打断);但在多核CPU 下,需要保证多个CPU不会同时修改同一个内存地址,解决方案是:
为CAS指令添加lock前缀 ,让CPU执行该指令时独占系统总线,禁止其他CPU修改该内存地址,从而保证多核CPU下CAS操作的原子性。

4. CAS的优缺点★(面试必背)

优点(乐观锁的核心优势)
  1. 无锁、非阻塞:无需加锁,线程失败时自旋重试,不进入阻塞状态;
  2. 无上下文切换开销:避免了锁的上下文切换、线程唤醒等开销,轻量高效;
  3. 粒度细:仅针对单个变量的原子操作,比锁的粗粒度操作更灵活。
缺点(面试必问,也是原子操作类的局限性)
  1. ABA问题:CAS的天然缺陷,已通过AtomicStampedReference解决;
  2. 自旋开销大:高并发下大量线程自旋重试,导致CPU空转,占用率飙升;
  3. 只能保证单个变量的原子性 :CAS仅能对单个变量实现原子操作,无法对多个变量、代码块实现原子性(若需要,需将多个变量封装成一个对象,用AtomicReference操作);
  4. 无法解决公平性问题:CAS是随机重试,无法保证线程的执行顺序,可能导致某些线程一直自旋失败。

子模块3.2 Unsafe类(Java的底层工具类,原子操作类的基石)

sun.misc.Unsafe是Java提供的底层本地方法类 ,可以理解为Java的"后门",直接操作内存、CPU指令、对象 ,是原子操作类的底层实现基石

1. Unsafe的核心作用(面试常考)

原子操作类的所有核心功能,最终都通过Unsafe实现,主要包括:

  1. 实现CAS操作 :提供compareAndSwapIntcompareAndSwapLongcompareAndSwapObject等native方法,是CAS的直接实现;
  2. 获取变量的内存偏移量 :通过objectFieldOffset方法获取对象字段的内存偏移量 ,后续CAS通过偏移量直接操作内存,无需通过对象的get/set方法,保证效率;
  3. 直接操作内存:可以直接读取、修改对象的内存值,绕过JVM的安全检查;
  4. 线程操作 :提供线程的挂起(park)、唤醒(unpark)方法,是LockSupport的底层实现。

2. Unsafe的关键注意点★(面试常考)

  1. 不能直接实例化 :Unsafe的构造方法是private的,无法通过new Unsafe()创建实例,必须通过反射获取;
  2. native方法:Unsafe的所有核心方法都是native的,直接与操作系统/CPU交互,跳过JVM的安全检查;
  3. 使用风险高 :直接操作内存和CPU,使用不当会导致内存泄漏、JVM崩溃、数据错乱,因此JDK对Unsafe的使用做了限制,普通开发中不建议直接使用;
  4. 原子操作类的专属工具:Unsafe是JUC原子操作类、锁框架的核心依赖,是Java并发的底层基础。

3. Unsafe的简单使用(了解即可,面试不要求手写)

java 复制代码
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeDemo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 1. 通过反射获取Unsafe的theUnsafe字段
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true); // 取消访问权限检查
        // 2. 获取Unsafe实例
        Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
        // 3. 示例:获取AtomicInteger的value字段的内存偏移量
        Field valueField = AtomicInteger.class.getDeclaredField("value");
        long valueOffset = unsafe.objectFieldOffset(valueField);
        System.out.println(valueOffset); // 输出value字段的内存偏移量
    }
}

子模块3.3 volatile关键字(保证可见性和有序性)★

volatile是Java的轻量级同步关键字,原子操作类的所有核心字段 (如AtomicInteger的value、LongAdder的base)都被volatile修饰,这是保证原子操作类线程安全的重要一环。

1. volatile的核心作用(面试必记,仅两个)

volatile仅保证可见性和有序性,不保证原子性(这是面试高频易错点),这两个作用正是原子操作类需要的:

(1)保证可见性

一个线程修改了volatile修饰的变量,其他线程能立即看到最新值 ,避免了多线程下的缓存一致性问题
原理:修改volatile变量时,JVM会强制将该变量的最新值从线程的工作内存刷新到主存,同时让其他线程的工作内存中该变量的缓存失效,其他线程必须从主存重新读取。

(2)保证有序性

禁止JVM对volatile修饰的变量进行指令重排 ,保证代码的执行顺序和编写顺序一致,避免线程读取到变量的中间值 (如i++的读-改-写步骤被重排)。
原理 :通过内存屏障实现,在volatile变量的读、写操作前后插入内存屏障,禁止指令重排。

2. 高频易错点★

volatile不保证原子性 :比如普通的volatile int i = 0;,多线程下执行i++仍然会有线程安全问题,因为i++是读-改-写三步,volatile只能保证每一步的可见性,无法保证三步的原子性。

原子操作类的解决方式volatile + CAS,volatile保证可见性和有序性,CAS保证读-改-写三步的原子性,两者结合才实现了完整的线程安全。

子模块3.4 CAS + Unsafe + volatile 三者的关联关系★(面试必背)

这是原子操作类底层原理的核心考点,用一句话总结:

volatile保证线程能看到变量的最新值(可见性),避免CAS读取到旧值;CAS保证变量的更新操作是原子的,避免多线程下的中间值覆盖;Unsafe提供底层实现,通过获取内存偏移量直接操作内存,调用CPU的CAS指令,是前两者的基础。

用流程图简化理解:

复制代码
线程读取变量 → volatile保证读取的是主存最新值 → 计算新值 → Unsafe通过内存偏移量执行CAS → CAS保证原子更新 → 成功则更新主存,失败则自旋重试

四、自旋锁(原子操作类的底层锁思想)

原子操作类的CAS实现,本质就是自旋锁 的典型应用,面试常考自旋锁的定义、思想、适用场景,以及与阻塞锁的对比。

1. 自旋锁的通俗定义

自旋锁是非阻塞锁 ,线程尝试获取锁失败时,不进入阻塞状态 (WAITING/BLOCKED),而是循环自旋(不断尝试CAS操作),直到获取锁成功为止。

2. 原子操作类中的自旋锁实现

以AtomicInteger的getAndIncrement()为例,它的底层就是循环CAS(自旋),伪代码如下:

java 复制代码
public final int getAndIncrement() {
    while (true) { // 自旋:失败就循环
        int current = get(); // 获取最新值
        int next = current + 1;
        if (compareAndSet(current, next)) { // CAS尝试更新
            return current; // 成功则返回,退出自旋
        }
        // 失败则继续循环,自旋重试
    }
}

核心思想:失败就重试,直到成功,不放弃CPU执行权。

3. 自旋锁的适用场景★(面试必问)

自旋锁的核心优势是无上下文切换开销 ,但自旋会占用CPU资源,因此仅适用于:
轻量级竞争、操作耗时极短的场景(如简单的计数、状态标记),此时自旋的开销远小于阻塞锁的上下文切换开销。

禁止使用场景:竞争激烈、操作耗时久的场景,此时大量线程自旋会导致CPU空转,占用率飙升,性能远低于阻塞锁。

4. 自旋锁 vs 阻塞锁(synchronized/Lock)★(面试必背)

特性 自旋锁(原子类/CAS) 阻塞锁(synchronized/ReentrantLock)
锁类型 非阻塞锁、乐观锁 阻塞锁、悲观锁
线程状态 运行中(RUNNABLE),自旋重试 阻塞状态(WAITING/BLOCKED),等待唤醒
核心开销 CPU自旋开销 上下文切换、线程唤醒开销
适用场景 轻量竞争、操作耗时极短 重度竞争、操作耗时久
实现原理 CAS循环重试 锁队列+阻塞/唤醒
性能 低并发下极高,高并发下极低 低并发下略低,高并发下稳定

五、原子操作类的使用场景★(面试常问「什么时候用?」)

原子操作类是无锁化的线程安全工具,适用于单个变量的简单操作 ,开发中常见的使用场景如下,同时标注避坑点,避免误用。

1. 核心适用场景

  1. 简单计数/统计:接口访问量、页面点击量、订单数、计数器(低并发用AtomicInteger/AtomicLong,高并发用LongAdder);
  2. 状态标记:开关状态(如服务的启动/停止,AtomicBoolean)、对象的状态更新(如订单的待支付/已支付,AtomicReference);
  3. 无锁化的对象字段更新:普通对象的非静态字段原子更新(如User的age字段,AtomicIntegerFieldUpdater),无需修改原对象代码;
  4. 解决ABA问题的场景:链表的原子入队/出队、带状态的对象更新(AtomicStampedReference);
  5. 自定义累加规则:高并发下求最大值、最小值、乘法(LongAccumulator);
  6. 单对象引用的原子更新:多线程下修改配置对象、核心对象的引用(AtomicReference)。

2. 高频避坑点★(面试/开发都要记)

这是原子操作类最容易误用的点,也是面试常考的易错点

原子操作类仅适用于 单个变量的原子操作,若需要保证 多个变量 代码块的原子性,必须使用synchronized或ReentrantLock,原子操作类无法解决这类问题。

反例 :多线程下同时更新xy两个变量,即使x和y都是AtomicInteger,也无法保证两个变量的更新是原子的,此时需要用锁包裹两个更新操作。

正例 :若要实现多个变量的原子更新,可将多个变量封装成一个对象,用AtomicReference操作该对象的引用,通过CAS原子更新整个对象(面试常考的解决方案)。


六、高频面试要点汇总(分题型,必背)

结合大厂面试考察重点,将原子操作类的面试题分为基础概念题、进阶原理题、手写题、场景分析题、易错点 ,标★的是必背题,直接考的概率极高。

题型1:基础概念题(面试开篇,必背)

  1. ★ 什么是原子操作?为什么i++不是原子操作?
  2. ★ Java中实现原子操作的方式有哪些?原子操作类与synchronized的区别是什么?
  3. ★ 原子操作类的包路径是什么?主要分为哪几类?
  4. ★ AtomicInteger的getAndIncrement()incrementAndGet()的区别是什么?
  5. 为什么AtomicBoolean底层是用int实现的?
  6. 数组类型原子类的操作粒度是什么?是否保证整个数组的原子性?

题型2:进阶原理题(面试核心,考察深度,必背)

  1. ★ 原子操作类的底层实现原理是什么?CAS、Unsafe、volatile分别起什么作用?
  2. ★ 什么是CAS?CAS的执行流程、优缺点分别是什么?
  3. ★ 什么是ABA问题?产生原因是什么?如何解决?AtomicStampedReference和AtomicMarkableReference的区别是什么?
  4. ★ JDK8为什么新增LongAdder?LongAdder比AtomicLong高效的原因是什么?底层原理是什么?
  5. ★ LongAdder的sum()方法为什么是最终一致性,而非实时一致性?适用场景是什么?
  6. ★ 对象字段更新原子类(AtomicXXXFieldUpdater)的使用条件有哪些?为什么要求字段是volatile的?
  7. ★ LongAdder和AtomicLong的区别是什么?分别适用于什么场景?
  8. Unsafe类的作用是什么?为什么不能直接实例化?
  9. 自旋锁的思想是什么?原子操作类如何体现自旋锁?自旋锁的适用场景和缺点是什么?
  10. CAS的底层是如何实现的?多核CPU下如何保证CAS的原子性?

题型3:手写编程题(考察实操,高频)

  1. ★ 手写一个基于AtomicInteger的线程安全计数器,实现incr()和getCount()方法;
  2. ★ 手写代码演示CAS的ABA问题,并使用AtomicStampedReference解决;
  3. 手写代码使用AtomicIntegerFieldUpdater更新普通对象的字段;
  4. 手写代码对比AtomicLong和LongAdder在高并发下的性能差异(用CountDownLatch模拟高并发);
  5. ★ 手写代码使用LongAccumulator实现高并发下的最大值/最小值统计;
  6. 手写代码用AtomicReference实现多个变量的原子更新(封装成对象)。

题型4:场景分析题(考察应用,大厂常考)

  1. ★ 高并发下统计接口的访问量,用AtomicLong还是LongAdder?为什么?
  2. ★ 金融场景中统计交易金额,用AtomicLong还是LongAdder?为什么?
  3. 链表的原子入队操作,需要解决什么问题?用哪个原子类?
  4. 如何实现多个变量的原子更新?(如同时更新x和y)
  5. 什么时候用AtomicStampedReference?什么时候用AtomicMarkableReference?
  6. 低并发下计数,用AtomicLong还是LongAdder?为什么?

题型5:高频易错点(面试避坑,必记)

  1. 误以为原子操作类能解决多个变量的原子性问题(实际仅支持单个变量);
  2. 误以为LongAdder的sum()方法是实时一致的(实际是最终一致,高并发下有延迟);
  3. 使用字段更新器时,字段未加volatile/设为private/static(导致创建失败/非原子);
  4. 误以为CAS一定比锁高效(高并发/操作耗时久时,CAS自旋开销>锁的上下文切换开销);
  5. 混淆AtomicStampedReference和AtomicMarkableReference的使用场景(版本号vs标记位);
  6. 误以为AtomicInteger的set()方法需要CAS(实际set是直接赋值,volatile保证可见性,无需CAS);
  7. 忘记LongAdder的cell数组是懒加载的(初始为空,高并发竞争时才初始化);
  8. 金融场景中误用LongAdder(需要实时精确计数,应使用AtomicLong);
  9. 误以为volatile保证原子性(实际仅保证可见性和有序性,需配合CAS)。

七、核心手写题示例(必写,面试直接考)

以下两个手写题是大厂面试高频直接考的题目,必须会写、会解释、会运行,附带详细注释和输出结果。

示例1:基于AtomicInteger的线程安全计数器

要求:实现incr()(原子+1)和getCount()(获取当前值)方法,测试10个线程各计数1000次,保证结果正确。

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

/**
 * 基于AtomicInteger的线程安全计数器(面试手写题必写)
 */
public class AtomicCounter {
    // 核心原子变量,AtomicInteger内部的value已被volatile修饰
    private final AtomicInteger count = new AtomicInteger(0);

    // 原子+1方法(开发最常用)
    public void incr() {
        count.incrementAndGet(); // 原子++i,返回新值
        // 若需要返回旧值,用count.getAndIncrement()
    }

    // 获取当前计数,volatile保证可见性
    public int getCount() {
        return count.get();
    }

    // 测试:10个线程,每个线程计数1000次
    public static void main(String[] args) throws InterruptedException {
        AtomicCounter counter = new AtomicCounter();
        int threadNum = 10; // 线程数
        int times = 1000; // 每个线程计数次数
        Thread[] threads = new Thread[threadNum];

        // 创建并启动线程
        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    counter.incr();
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完成
        for (Thread t : threads) {
            t.join();
        }

        // 输出结果:10000(线程安全,无计数丢失)
        System.out.println("最终计数结果:" + counter.getCount());
    }
}

输出结果最终计数结果:10000(线程安全,无丢失)。

示例2:演示ABA问题并使用AtomicStampedReference解决

要求:用两个线程演示ABA问题,线程1准备将1→2,线程2执行1→3→1的ABA操作,使用AtomicStampedReference让线程1的CAS失败,成功解决ABA问题。

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

/**
 * 演示CAS的ABA问题,并使用AtomicStampedReference解决(面试手写题必写)
 */
public class ABADemo {
    // 初始化:值为1,初始版本戳为1
    private static final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) throws InterruptedException {
        // 线程1:准备将值从1→2,预期版本戳1
        Thread t1 = new Thread(() -> {
            // 获取当前值和版本戳
            int stamp = asr.getStamp();
            Integer ref = asr.getReference();
            System.out.println("线程1-获取值:" + ref + ",版本戳:" + stamp);

            try {
                Thread.sleep(1000); // 休眠1秒,让线程2执行ABA操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 尝试CAS:值1→2,版本戳1→2(全匹配才更新)
            boolean success = asr.compareAndSet(1, 2, stamp, stamp + 1);
            System.out.println("线程1-CAS结果:" + success + ",当前值:" + asr.getReference() + ",当前版本戳:" + asr.getStamp());
        });

        // 线程2:执行ABA操作:1→3→1,版本戳1→2→3
        Thread t2 = new Thread(() -> {
            int stamp = asr.getStamp();
            Integer ref = asr.getReference();
            System.out.println("线程2-获取值:" + ref + ",版本戳:" + stamp);

            // 第一步:1→3,版本戳1→2
            asr.compareAndSet(1, 3, stamp, stamp + 1);
            System.out.println("线程2-将1→3,当前版本戳:" + asr.getStamp());

            // 第二步:3→1,版本戳2→3(先获取最新版本戳)
            stamp = asr.getStamp();
            asr.compareAndSet(3, 1, stamp, stamp + 1);
            System.out.println("线程2-将3→1,当前版本戳:" + asr.getStamp());
        });

        // 启动线程
        t1.start();
        t2.start();
        // 等待线程执行完成
        t1.join();
        t2.join();
    }
}

输出结果

复制代码
线程1-获取值:1,版本戳:1
线程2-获取值:1,版本戳:1
线程2-将1→3,当前版本戳:2
线程2-将3→1,当前版本戳:3
线程1-CAS结果:false,当前值:1,当前版本戳:3

核心说明 :线程1的CAS因版本戳不匹配(预期1,实际3)执行失败,即使值变回了1,也成功解决了ABA问题。


  1. 原子操作类是无锁化 的线程安全工具,基于CAS+Unsafe+volatile实现,适用于单个变量的原子更新;
  2. 原子操作类分为5大类,其中基本类型、引用类型、字段更新器、JDK8累加器是面试重点,LongAdder是高并发计数的首选;
  3. CAS是原子操作类的底层核心,存在ABA问题,通过AtomicStampedReference(版本戳) 全量解决;
  4. JDK8的LongAdder通过分段CAS(base+cell数组) 解决AtomicLong的高并发自旋瓶颈,sum()是最终一致性,适用于非金融的统计场景;
  5. 字段更新器基于反射实现,要求字段volatile+非private+非static
  6. CAS+Unsafe+volatile三者缺一不可:volatile保证可见性,CAS保证原子性,Unsafe提供底层实现;
  7. 原子操作类的CAS是自旋锁的典型实现,适用于轻量竞争、操作耗时极短的场景;
  8. 原子操作类仅支持单个变量的原子操作,多变量/代码块的原子性需要用synchronized/Lock;
  9. LongAdder vs AtomicLong:低并发用AtomicLong(精确),高并发非金融场景用LongAdder(高效),金融场景必须用AtomicLong(实时精确)。
相关推荐
沛沛老爹2 小时前
Web开发者转型AI安全实战:Agent Skills敏感数据脱敏架构设计
java·开发语言·人工智能·安全·rag·skills
cyforkk2 小时前
03、Java 基础硬核复习:流程控制语句的核心逻辑与面试考点
java·开发语言·面试
星火开发设计2 小时前
const 指针与指针 const:分清常量指针与指针常量
开发语言·c++·学习·算法·指针·const·知识
0x532 小时前
JAVA|智能无人机平台(一)
java·开发语言·无人机
雨季6662 小时前
构建 OpenHarmony 文本高亮关键词标记器:用纯字符串操作实现智能标注
开发语言·javascript·flutter·ui·ecmascript·dart
2501_948120152 小时前
Java实现的SSL/TLS协议通信系统
java·开发语言·ssl
b2077212 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 个人中心实现
android·java·python·flutter·harmonyos
cici158742 小时前
基于MATLAB的TERCOM算法实现与优化
开发语言·matlab
天上飞的粉红小猪2 小时前
c++的IO流
开发语言·c++