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

相关推荐
野犬寒鸦2 小时前
从零起步学习MySQL || 第十六章:MySQL 分库分表的考量策略
java·服务器·数据库·后端·mysql
木风小助理2 小时前
JavaAtomicInteger底层实现深度解析
java
BD_Marathon2 小时前
搭建MyBatis框架之创建MyBatis的映射文件(五)
java·数据库·mybatis
一只叫煤球的猫2 小时前
为什么Java里面,Service 层不直接返回 Result 对象?
java·spring boot·面试
洛阳泰山2 小时前
智能体项目MaxKB4J - 本地部署与开发完整指南
java·agent·工作流·rag·智能体·maxkb
Solar20252 小时前
机械制造业TOB企业获客软件选型指南:从挑战到解决方案的深度解析
java·大数据·服务器·架构·云计算
星火开发设计2 小时前
C++ stack 全面解析与实战指南
java·数据结构·c++·学习·rpc··知识
宋情写3 小时前
JavaAI06-SpringAI
java·人工智能
Hello.Reader3 小时前
Flink Avro Format Java / PyFlink 读写、Schema 细节与坑点总结
java·python·flink