【面试专栏|Java并发编程】Java 原子类全解:AtomicInteger、LongAdder 原理与适用场景


🍃 予枫个人主页
📚 个人专栏 : 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南

💻 Debug 这个世界,Return 更好的自己!


引言

家人们谁懂啊!Java并发面试里,原子类绝对是"常客",尤其是AtomicInteger和LongAdder,面试官必问"两者有啥区别""什么时候用哪个"。很多人只会用AtomicInteger,对LongAdder一知半解,被追问原理直接卡壳。今天就接地气拆解,从底层原理、实战用法到适用场景,再加上面试追问,帮你彻底吃透这两个核心原子类!

文章目录

一、JAVA原子类核心认知

先一句话搞懂:Java原子类是无需加锁就能保证并发安全的工具类,底层依赖CAS机制(之前讲过的Compare And Swap)或分段累加思想,解决多线程下变量修改的竞态问题,避免synchronized的锁开销,广泛用于并发编程场景。

核心特点(敲重点💡):

  • 原子性:保证变量的修改操作是原子的,不会出现多线程并发修改的中间态
  • 无锁化:大部分原子类基于CAS实现,无需加锁,性能优于synchronized
  • 易用性:API简洁,直接调用方法就能实现原子操作,无需手动处理并发安全

常见的Java原子类分类(简单提一嘴,重点讲核心两个):

  1. 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean(最常用)
  2. 引用类型原子类:AtomicReference、AtomicStampedReference(解决ABA问题)
  3. 累加器类:LongAdder、DoubleAdder(高并发场景首选)

二、ATOMICINTEGER 原理与实战

AtomicInteger是最常用的原子类,核心作用是保证int类型变量的原子修改,底层完全依赖CAS机制实现,也是我们入门原子类的首选。

2.1 核心原理(结合源码,不搞复杂)

AtomicInteger的底层核心是「CAS + 自旋」,和之前讲的CAS原理一脉相承,来看关键源码(JDK8):

java 复制代码
public class AtomicInteger extends Number implements java.io.Serializable {
    // 底层依赖Unsafe工具类,直接操作内存
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 变量值在内存中的偏移量,用于定位内存地址
    private static final long valueOffset;

    static {
        try {
            // 计算value变量的内存偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // 要操作的核心变量,用volatile修饰,保证可见性
    private volatile int value;

    // 核心原子自增方法,底层调用CAS
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // 核心CAS方法(底层调用CPU原子指令)
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

关键解读(用大白话讲,不绕弯):

  • value变量用volatile修饰:保证多线程下变量的可见性,一个线程修改后,其他线程能立即看到最新值
  • 依赖Unsafe工具类:直接操作内存地址,实现底层CAS操作
  • 自旋机制:当CAS修改失败时,会循环重试,直到修改成功(比如incrementAndGet方法,本质是循环CAS)

2.2 实战用法(直接上手,复制就能用)

话不多说,上代码,常见的原子操作(自增、自减、比较并交换)都给大家写好了:

java 复制代码
public class AtomicIntegerDemo {
    public static void main(String[] args) throws InterruptedException {
        // 初始化原子类,初始值为0
        AtomicInteger atomicInt = new AtomicInteger(0);

        // 1. 原子自增(常用,多线程下安全)
        atomicInt.incrementAndGet(); // 结果:1
        // 2. 原子自减
        atomicInt.decrementAndGet(); // 结果:0
        // 3. 原子累加(加5)
        atomicInt.addAndGet(5); // 结果:5
        // 4. 比较并交换(预期值5,修改为10,成功返回true)
        boolean success = atomicInt.compareAndSet(5, 10);
        System.out.println("CAS修改是否成功:" + success); // true
        System.out.println("最终值:" + atomicInt.get()); // 10

        // 多线程测试(验证线程安全)
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                atomicInt.incrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                atomicInt.incrementAndGet();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("多线程自增后的值:" + atomicInt.get()); // 2010(无线程安全问题)
    }
}

2.3 AtomicInteger执行流程(mermaid图直观看)



初始化AtomicInteger,value=0
线程调用incrementAndGet方法
重新获取内存中value的当前值v,自旋重试
CAS比较:内存值v == 预期值v?
将内存值修改为v+1,操作成功
返回修改后的值v+1

三、LONGADDER 原理与实战

LongAdder是JDK8新增的原子累加器,专门解决高并发场景下AtomicInteger(或AtomicLong)自旋开销过大的问题,性能比AtomicInteger更优,尤其适合"高并发、多线程频繁累加"的场景。

3.1 核心原理(重点:分段累加,减少自旋)

LongAdder的核心思想是「分段累加」,打破了AtomicInteger"单个变量CAS自旋"的瓶颈,具体原理如下:

  1. 内部维护一个数组(cells),每个数组元素都是一个独立的累加单元(类似一个小的计数器)
  2. 多线程并发累加时,会通过哈希算法,将线程分配到不同的累加单元(cells[i])
  3. 每个线程只操作自己对应的累加单元,减少CAS自旋冲突(不再所有线程竞争同一个变量)
  4. 当需要获取最终结果时,将所有累加单元的值相加,得到总结果

简单说:AtomicInteger是"一个人干所有活,大家都抢着用",LongAdder是"多个人分工干,每人干一点,最后汇总",高并发下效率自然更高。

来看核心源码(简化版,看懂关键即可):

java 复制代码
public class LongAdder extends Striped64 implements Serializable {
    // 累加操作,核心方法
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        // 1. 先尝试操作base变量(低并发时,类似AtomicLong,减少数组开销)
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            // 2. 高并发时,获取当前线程对应的Cell,进行累加
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                // 3. 若Cell为空或CAS失败,扩容数组或创建新Cell
                longAccumulate(x, null, uncontended);
        }
    }

    // 获取最终结果:汇总所有Cell的值 + base的值
    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (Cell c : as) {
                if (c != null)
                    sum += c.value;
            }
        }
        return sum;
    }

    // 内部累加单元,每个Cell都是一个独立的CAS单元
    static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }
        // ... 省略其他代码
    }
}

3.2 实战用法(和AtomicInteger类似,上手简单)

LongAdder的API和AtomicInteger基本一致,使用起来几乎没有学习成本,重点看高并发场景的表现:

java 复制代码
public class LongAdderDemo {
    public static void main(String[] args) throws InterruptedException {
        // 初始化LongAdder,初始值为0
        LongAdder longAdder = new LongAdder();

        // 1. 累加(常用,和AtomicInteger的addAndGet类似)
        longAdder.add(5);
        // 2. 自增(等价于add(1))
        longAdder.increment();
        // 3. 自减(等价于add(-1))
        longAdder.decrement();
        // 4. 获取最终结果
        System.out.println("当前累加值:" + longAdder.sum()); // 5

        // 高并发测试(10个线程,每个线程累加10000次)
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    longAdder.add(1);
                }
            });
        }

        // 启动所有线程
        for (Thread thread : threads) {
            thread.start();
        }
        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            thread.join();
        }

        // 输出最终结果(预期10*10000=100000,无线程安全问题)
        System.out.println("高并发累加后的值:" + longAdder.sum()); // 100000
    }
}

3.3 LongAdder执行流程(mermaid图拆解)





初始化LongAdder,base=0,cells=null
线程调用add方法
低并发?
CAS修改base变量,base += x
通过哈希获取线程对应的Cell单元
Cell存在且CAS成功?
Cell.value += x
扩容cells数组或创建新Cell
获取结果时,sum=base + 所有Cell的值

四、ATOMICINTEGER VS LONGADDER(核心区别,面试必背)

这部分是面试高频考点,别死记硬背,结合场景理解,一张表搞懂所有区别:

对比维度 AtomicInteger LongAdder
底层原理 基于CAS + 自旋,操作单个变量 基于分段累加(cells数组)+ CAS,多单元分工
并发性能 低并发优秀,高并发自旋冲突多,性能下降 高并发优秀,分段累加减少冲突,性能更稳定
内存开销 较小(仅维护一个value变量) 较大(维护cells数组,高并发时数组会扩容)
适用场景 低并发、需频繁获取精确值、原子操作多样(自增、自减、CAS) 高并发、仅需累加/自增/自减,无需频繁获取精确值
API丰富度 丰富(incrementAndGet、compareAndSet等) 简单(主要是add、increment、sum等)

补充一句大白话:如果你的场景是"偶尔修改,经常读值",用AtomicInteger;如果是"高并发频繁修改,偶尔读值",用LongAdder。比如:统计接口访问量(高并发累加),用LongAdder;统计线程执行状态(偶尔修改,经常判断),用AtomicInteger。

五、面试官追问环节(实战价值拉满,直接背)

整理了3个高频追问,附标准答案,面试时遇到直接答,不用慌!

追问1:AtomicInteger为什么能保证线程安全?底层依赖什么?

核心答案:AtomicInteger的线程安全,底层依赖「CAS机制 + volatile修饰」。

  1. volatile修饰value变量:保证多线程下变量的可见性,一个线程修改后,其他线程能立即看到最新值,避免脏读。
  2. CAS机制:基于CPU原子指令(cmpxchg),实现"比较并交换"的原子操作,无需加锁,避免线程阻塞。
  3. 自旋重试:当CAS修改失败时,会循环重试,直到修改成功,保证最终能完成原子操作。

追问2:LongAdder为什么比AtomicInteger在高并发下性能更好?

核心答案:两者的核心区别在于「冲突粒度不同」。

  1. AtomicInteger所有线程竞争同一个value变量,高并发时CAS自旋冲突严重,大量线程空转,占用CPU资源。
  2. LongAdder采用"分段累加",将一个变量拆分为多个Cell单元,多线程分散到不同Cell操作,减少CAS冲突,自旋次数大幅减少,高并发下性能更优。
  3. 补充:低并发时,LongAdder会先操作base变量,和AtomicInteger性能差不多,避免数组扩容带来的开销。

追问3:什么时候用AtomicInteger,什么时候用LongAdder?举个实际业务场景。

核心答案:根据「并发量」和「操作场景」选择,结合业务举例更加分。

  1. 用AtomicInteger的场景:低并发、需要精确控制原子操作(比如CAS比较并交换)、频繁获取变量精确值。
    举例:线程池的核心线程数控制、并发场景下的开关状态(true/false)、低并发的计数器(比如后台任务执行次数)。
  2. 用LongAdder的场景:高并发、仅需要累加/自增/自减操作、不需要频繁获取精确值。
    举例:网站访问量统计、接口请求次数统计、高并发任务的执行次数统计(比如秒杀活动的下单次数)。

六、总结

Java原子类是并发编程的"轻量级神器",AtomicInteger基于CAS自旋,适合低并发、多原子操作场景;LongAdder基于分段累加,适合高并发、纯累加场景,两者各有优势,核心是根据业务场景选择。

面试时,不仅要讲清两者的原理,还要能区分适用场景,再结合面试官追问的知识点,就能轻松拿下这个考点。记住:没有最好的工具,只有最适合的场景!

相关推荐
ok_hahaha1 小时前
java-从头开始-苍穹外卖-day08提交订单
java
东离与糖宝2 小时前
微软BitNet开源:用Java在边缘设备部署7B级本地大模型(含ONNX Runtime优化)
java·人工智能
Seven972 小时前
Java对象头:深入理解对象存储的核心机制
java
!停2 小时前
C++入门基础
java·开发语言·c++
赵文宇(温玉)2 小时前
OpenClaw3.13已经发布,该如何快速升级
java·开发语言
Java爱好狂.2 小时前
IT界有哪些优秀的高并发解决方案?
java·高并发·多线程·java面试·java面试题·后端开发·java八股文
代码雕刻家2 小时前
3.3.Maven-idea集成-配置及创建Maven项目
java·maven·intellij-idea
爆更小哇2 小时前
JMeter配置和使用入门指南
java·开发语言·测试工具·jmeter·自动化
kaico20182 小时前
jenkins值之job的配置
java·jenkins