并发编程(二):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() 方法返回的值只是一个近似准确值(在并发极高时,统计过程中可能有新的累加发生),但在统计计数等场景下完全够用且性能极佳。

相关推荐
what丶k17 小时前
深度解析:以Kafka为例,消息队列消费幂等性的实现方案与生产实践
java·数据结构·kafka
星火开发设计17 小时前
C++ 输入输出流:cin 与 cout 的基础用法
java·开发语言·c++·学习·算法·编程·知识
毕设源码-邱学长17 小时前
【开题答辩全过程】以 基于Springboot的酒店住宿信息管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
仟濹17 小时前
【Java加强】1 异常 | 打卡day1
java·开发语言·python
AllData公司负责人18 小时前
【亲测好用】实时开发平台能力演示
java·c语言·数据库
pcm12356718 小时前
设计C/S架构的IM通信软件(3)
java·c语言·架构
咖啡啡不加糖18 小时前
Grafana 监控服务指标使用指南:打造可视化监控体系
java·后端·grafana
€81118 小时前
Java入门级教程26——序列化和反序列化,Redis存储Java对象、查询数据库与实现多消费者消息队列
java·拦截器·序列化和反序列化·数据库查询·redis存储java对象·多消费者消息队列
多多*18 小时前
Mysql数据库相关 事务 MVCC与锁的爱恨情仇 锁的层次架构 InnoDB锁分析
java·数据库·windows·sql·oracle·面试·哈希算法
cyforkk18 小时前
15、Java 基础硬核复习:File类与IO流的核心逻辑与面试考点
java·开发语言·面试