并发编程(二):Java原子类(Atomic Classes)全解析

文章目录

在多线程并发编程的浩瀚宇宙中,线程安全 始终是核心议题。当我们需要对一个变量进行简单的累加操作时,volatile 关键字虽然能保证可见性,却无法保证原子性;而 synchronized 锁虽然强大,但在高并发场景下往往伴随着沉重的上下文切换开销。

这时,Java原子类(Atomic Classes) 应运而生。它们位于 java.util.concurrent.atomic 包下,利用底层硬件的 CAS 机制,以一种无锁(Lock-Free)的高效方式,解决了并发场景下的原子性问题。


1. 核心原理:CAS (Compare-And-Swap)

原子类的魔法源自于 CAS。它是一条 CPU 并发原语。

什么是 CAS

关于CAS的原理可以看这篇:https://blog.csdn.net/Tracycoder/article/details/156860586?spm=1011.2415.3001.10575\&sharefrom=mp_manage_link

Java 中的 Unsafe 类

Java 无法直接访问底层操作系统,而是通过 sun.misc.Unsafe 类来操作内存。原子类内部正是调用了 Unsafe 的 compareAndSwap 系列方法。


2. 原子类家族谱系

JDK 提供了丰富的原子类,主要可以分为以下四类:

分类 包含类 描述
基本类型 AtomicInteger, AtomicLong, AtomicBoolean 直接更新基本类型数据。
数组类型 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray 原子更新数组中的某个元素。
引用类型 AtomicReference, AtomicStampedReference, AtomicMarkableReference 原子更新对象引用,解决ABA问题。
对象属性 AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 原子更新对象的某个字段。

3. 实战演练:AtomicInteger

AtomicInteger 是最常用的原子类。让我们对比一下使用 intAtomicInteger 在多线程下的表现。

场景:多线程计数器

假设有 100 个线程,每个线程对计数器累加 1000 次。

错误示范:普通 int
java 复制代码
public class UnsafeCounter {
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count++; // 非原子操作:读 -> 改 -> 写
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();
        
        // 预期结果:100000,实际结果通常小于 100000
        System.out.println("Final Count (Unsafe): " + count);
    }
}
正确示范:AtomicInteger
java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class SafeCounter {
    // 使用原子类
    private static AtomicInteger atomicCount = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    // 相当于 i++,但在硬件层面保证原子性
                    atomicCount.getAndIncrement(); 
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();

        // 结果始终为 100000
        System.out.println("Final Count (Atomic): " + atomicCount.get());
    }
}

源码剖析:getAndIncrement

AtomicIntegergetAndIncrement 是如何实现的?

java 复制代码
// JDK 8 源码片段
public final int getAndIncrement() {
    // this: 当前对象
    // valueOffset: value 字段在内存中的偏移量
    // 1: 增加的值
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// Unsafe 类中的实现 (模拟)
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        // 1. 获取内存中当前的值
        v = getIntVolatile(o, offset);
        // 2. 循环尝试 CAS 操作
        // 如果内存值还是 v,则更新为 v + delta,返回 true 退出循环
        // 否则(说明被其他线程改了),返回 false,继续循环(自旋)
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

5. 高性能利器:LongAdder (JDK 8+)

虽然 AtomicLong 使用 CAS 保证了原子性,但在超高并发下,大量线程同时竞争更新同一个变量,会导致大量 CAS 失败并自旋,消耗 CPU 资源。

JDK 8 引入了 LongAdder

设计思想:分段锁(空间换时间)

LongAdder 内部维护了一个 base 变量和一个 Cell[] 数组。

  • 无竞争时:直接更新 base
  • 有竞争时:线程被哈希映射到 Cell 数组的某个槽位,对该槽位的值进行 CAS 更新。
  • 获取最终结果时:sum = base + sum(Cell[])

AtomicLong vs LongAdder 性能对比示意

LongAdder
CAS
Hash
Hash
Hash
Thread A
Base
Thread B
Cell 1
Thread C
Cell 2
Thread D
AtomicLong
CAS 竞争
CAS 竞争
CAS 竞争
CAS 竞争
Thread 1
Value
Thread 2
Thread 3
Thread 4

代码示例
java 复制代码
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {
    public static void main(String[] args) {
        LongAdder adder = new LongAdder();
        
        // 多个线程同时累加
        adder.increment();
        adder.add(10);
        
        // 获取总和
        System.out.println("Sum: " + adder.sum());
    }
}

注意: LongAddersum() 方法返回的值只是一个近似准确值(在并发极高时,统计过程中可能有新的累加发生),但在统计计数等场景下完全够用且性能极佳。

相关推荐
San813_LDD6 小时前
[C语言]《Dev-C++ 报错解决手册(Day0607 精华版)》
java·前端·javascript
Anastasiozzzz7 小时前
从有限状态机到智能体图:传统 FSM 与 Agent Graph的演进
java·人工智能·python·ai
wang090714 小时前
自己动手写一个spring之IOC_2
java·后端·spring
来杯@Java14 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
不知名的老吴15 小时前
线程的生命周期之线程“插队“
java·开发语言·python
ANnianStriver15 小时前
PetLumina-02-后端开发与前后端联调
java·ai·sa-token
杨了个杨898216 小时前
Keepalived + Nginx + HAProxy 高可用架构部署实战案例
java·nginx·架构
马士兵教育18 小时前
Java还有前景吗?Java+AI大模型学习路线及项目?
java·人工智能·python·学习·机器学习
snow@li18 小时前
Java:理解 Gradle / 后端项目的管家 / 打包SpringBoot 应用 / 完成编译、下载依赖、运行测试、打包 JAR/WAR / 速查表
java