从 CAS 到 Unsafe,再到原子类:深入剖析 Java 并发核心

从 CAS 到 Unsafe,再到原子类:深入剖析 Java 并发核心

最近面试中聊到并发问题,我从 CAS(Compare-And-Swap)讲到 Unsafe 类,再延伸到原子类,收获颇多。复盘下来,我觉得这个话题值得系统化整理,于是写了这篇博客。内容会从 CAS 的原理入手,深入到 CPU 指令的记忆技巧,再全面剖析 Unsafe 包,最后系统介绍原子类家族,形成一个完整的知识体系。


一、CAS 是什么?

CAS(比较并交换)是无锁并发编程的核心机制,用于在多线程环境下保证操作的原子性。它的逻辑很简单:先比较变量当前值是否符合预期,如果是就更新为新值,否则失败并重试

CAS 的三个参数:

  • V:内存位置(变量的地址)。
  • A:旧的预期值。
  • B:新的目标值。

流程:

  • 如果 V == A,则 V = B,返回 true。
  • 如果 V != A,则返回 false,通常需要自旋重试。

例子: 变量 value = 5,线程 1 用 CAS 将其改为 6:

  • V = value 的地址,A = 5,B = 6。
  • 检查 value == 5,成功则更新为 6;若已被改为 7,则失败。

底层支持: CAS 依赖 CPU 的原子指令,比如 x86 的 cmpxchg(Compare and Exchange)。Java 通过 Unsafe 类调用这些指令。

CPU 指令怎么记忆?cmpxchg 太长了吧?

确实,cmpxchg 看起来复杂,但拆解后不难记:

  • cmp:Compare(比较)。
  • xchg:Exchange(交换)。
  • 合起来就是"比较并交换",正好对应 CAS 的逻辑。

记忆技巧:

  1. 联想缩写:把它想象成"cmp + xchg",就像"比较后换值"。
  2. 场景绑定 :想到 CAS 就联想到 cmpxchg,它是硬件级的 CAS。
  3. 简化记忆:不记全名,只记功能------"原子比较交换指令"。

x86 还有其他相关指令,比如 lock 前缀(确保原子性),但 cmpxchg 是最核心的。现代 CPU 还会结合缓存一致性协议(如 MESI)保证多核下的正确性。

CAS 的优缺点:

  • 优点:无锁,高并发下性能优于锁。
  • 缺点:
    • ABA 问题:值从 A 变 B 又变回 A,CAS 察觉不到。
    • 自旋开销:竞争激烈时 CPU 占用高。
    • 单变量限制:无法原子化多个变量。

二、Unsafe 类:Java 的底层魔法工具

CAS 的实现离不开 sun.misc.Unsafe 类。它是 JDK 内部的一个非公开类,提供了直接操作内存、线程、对象的能力,是 JUC(Java Util Concurrent)包的基础。Unsafe 不建议直接使用,但理解它能让我们洞悉并发工具的底层逻辑。

Unsafe 的功能全貌

Unsafe 的能力远超 CAS,可以分为以下几大类:

  1. 内存操作

    • allocateMemory(long bytes) / freeMemory(long address):分配/释放堆外内存。
    • putInt(long address, int value) / getInt(long address):按地址读写数据。
    • 作用:支持堆外数据结构(如 Netty 的 DirectByteBuffer)。
    • 例子unsafe.putInt(0x1234, 42) 直接往内存地址写值。
  2. CAS 操作

    • compareAndSwapInt(Object obj, long offset, int expected, int update):原子更新 int。

    • compareAndSwapLong / compareAndSwapObject:支持 long 和对象。

    • 实现 :通过 JNI 调用 CPU 的 cmpxchg,实现无锁操作。

    • 例子

      java 复制代码
      unsafe.compareAndSwapInt(this, valueOffset, 5, 6);
  3. 对象与字段操作

    • objectFieldOffset(Field f):获取字段的内存偏移量。
    • getObject(Object obj, long offset) / putObject:读写字段值。
    • 作用:绕过访问权限,支持反射和原子更新。
    • 例子unsafe.getInt(obj, offset) 读取私有字段。
  4. 线程控制

    • park(boolean isAbsolute, long time):挂起线程。
    • unpark(Object thread):唤醒线程。
    • 作用:LockSupport 的底层实现,用于构建锁和同步工具。
  5. 类与实例操作

    • defineClass:动态定义类。
    • allocateInstance(Class<?> cls):不调用构造器创建对象。
    • 作用:支持框架(如 Spring 的代理生成)。
  6. 内存屏障

    • loadFence() / storeFence() / fullFence():控制内存访问顺序。
    • 作用:确保 volatile 语义和指令重排优化。
Unsafe 如何支持 CAS?

compareAndSwapInt 为例:

java 复制代码
public final native boolean compareAndSwapInt(Object obj, long offset, int expected, int update);
  • obj 是目标对象,offset 是字段偏移量。
  • 通过 JNI 调用 C++ 的原子操作,最终映射到 cmpxchg
  • volatile 配合使用(如 getIntVolatile),确保内存可见性。
Unsafe 的价值
  • 性能:接近 C 的操作效率。
  • 灵活性:突破 JVM 限制。
  • 基础性:它是原子类、AQS、NIO 等功能的基石。

三、原子类体系:基于 Unsafe 的并发工具

Java 的 java.util.concurrent.atomic 包封装了 Unsafe,提供了丰富的原子类,形成一个体系化的工具集。以下按类别详细介绍,方便记忆。

1. 基本类型原子类
  • AtomicInteger / AtomicLong / AtomicBoolean

    • 核心字段

      java 复制代码
      private volatile int value;
      private static final long valueOffset; // Unsafe 计算偏移量
    • 关键方法

      • getAndIncrement():自增并返回旧值,用 CAS 实现。

        java 复制代码
        do {
            v = unsafe.getIntVolatile(this, valueOffset);
        } while (!unsafe.compareAndSwapInt(this, valueOffset, v, v + 1));
      • compareAndSet(expected, update):单次 CAS。

      • getAndAdd(int delta):加 delta 并返回旧值。

    • 应用:计数器、开关控制。

  • 记忆点:基础原子类,操作单个数字或布尔值。

2. 引用类型原子类
  • AtomicReference

    • 核心字段

      java 复制代码
      private volatile V value;
    • 关键方法

      • compareAndSet(V expect, V update):更新引用。
      • 应用:无锁数据结构(如链表头节点)。
    • 例子

      java 复制代码
      AtomicReference<Node> head = new AtomicReference<>();
      head.compareAndSet(null, new Node());
  • AtomicStampedReference / AtomicMarkableReference

    • 特点:解决 ABA 问题。
    • 核心字段:值 + 版本号(stamp)/标记(mark)。
    • 关键方法
      • compareAndSet(V expect, V update, int expStamp, int newStamp)
    • 应用:需要版本控制的场景。
  • 记忆点:引用类扩展,解决复杂对象和 ABA。

3. 数组类型原子类
  • AtomicIntegerArray / AtomicLongArray / AtomicReferenceArray

    • 核心字段

      java 复制代码
      private final int[] array;
      private static final int base; // 数组基地址
    • 关键方法

      • getAndSet(int i, int newValue):更新数组元素。

        java 复制代码
        long offset = checkedByteOffset(i);
        while (!unsafe.compareAndSwapInt(array, offset, current, newValue));
    • 应用:多线程操作数组元素。

  • 记忆点:数组扩展,批量原子操作。

4. 字段更新器
  • AtomicIntegerFieldUpdater / AtomicLongFieldUpdater / AtomicReferenceFieldUpdater

    • 特点:更新对象中的 volatile 字段。

    • 用法

      java 复制代码
      AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "count");
      updater.compareAndSet(obj, 5, 6);
    • 应用:只更新字段,避免创建新对象。

  • 记忆点:字段级原子操作,灵活但有 volatile 限制。

5. 高竞争优化类
  • LongAdder / LongAccumulator

    • 特点:高并发下替代 AtomicLong,分段累加减少 CAS 冲突。

    • 核心实现

      java 复制代码
      private volatile long base; // 基础值
      private volatile Cell[] cells; // 分段数组
    • 关键方法

      • add(long x):分散更新到 Cell,最后 sum() 汇总。
    • 应用:统计计数(如 QPS)。

  • DoubleAdder / DoubleAccumulator

    • 特点:类似 LongAdder,支持浮点数。
    • 应用:金融计算。
  • 记忆点:性能优化,适合高竞争场景。

原子类体系总结
类别 代表类 特点 应用场景
基本类型 AtomicInteger, AtomicLong 单变量原子操作 计数器、状态标记
引用类型 AtomicReference, Stamped 对象引用更新,防 ABA 无锁数据结构
数组类型 AtomicIntegerArray 数组元素原子操作 多线程数组处理
字段更新器 AtomicIntegerFieldUpdater 对象字段原子更新 精细化并发控制
高竞争优化 LongAdder, DoubleAdder 分段累加,高性能 高并发统计

记忆技巧:

  1. 分层记忆:从简单(基本类型)到复杂(引用、数组、字段、高竞争)。
  2. 场景驱动:想到"计数器"就联到 AtomicInteger,"高并发统计"联到 LongAdder。
  3. 递进关系:Unsafe → CAS → 原子类,层层封装。

四、深入思考与总结

  1. CPU 指令与 CAS
    cmpxchg 是 CAS 的硬件基础,通过"比较+交换"一步完成。记住它的功能比记名字更重要。

  2. Unsafe 的全面性

    Unsafe 不仅是 CAS 的桥梁,还支持内存、线程、对象操作,是 Java 并发的底层支撑。

  3. 原子类的体系化

    从基本类型到高竞争优化,原子类覆盖了各种并发需求,形成一个由简单到复杂的工具链。

相关推荐
codingandsleeping3 小时前
浏览器的缓存机制
前端·后端
追逐时光者4 小时前
面试官问:你知道 C# 单例模式有哪几种常用的实现方式?
后端·.net
Asthenia04124 小时前
Numpy:数组生成/modf/sum/输出格式规则
后端
Asthenia04124 小时前
NumPy:数组加法/数组比较/数组重塑/数组切片
后端
Asthenia04124 小时前
Numpy:limspace/arange/数组基本属性分析
后端
Asthenia04124 小时前
Java中线程暂停的分析与JVM和Linux的协作流程
后端
Asthenia04124 小时前
Seata TCC 模式:RootContext与TCC专属的BusinessActionContext与TCC注解详解
后端
自珍JAVA4 小时前
【代码】zip压缩文件密码暴力破解
后端
今夜有雨.5 小时前
HTTP---基础知识
服务器·网络·后端·网络协议·学习·tcp/ip·http
Asthenia04125 小时前
Seata TCC 模式的空回滚与悬挂问题之解决方案-结合时序分析
后端