从 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 的逻辑。
记忆技巧:
- 联想缩写:把它想象成"cmp + xchg",就像"比较后换值"。
- 场景绑定 :想到 CAS 就联想到
cmpxchg
,它是硬件级的 CAS。 - 简化记忆:不记全名,只记功能------"原子比较交换指令"。
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,可以分为以下几大类:
-
内存操作
allocateMemory(long bytes)
/freeMemory(long address)
:分配/释放堆外内存。putInt(long address, int value)
/getInt(long address)
:按地址读写数据。- 作用:支持堆外数据结构(如 Netty 的 DirectByteBuffer)。
- 例子 :
unsafe.putInt(0x1234, 42)
直接往内存地址写值。
-
CAS 操作
-
compareAndSwapInt(Object obj, long offset, int expected, int update)
:原子更新 int。 -
compareAndSwapLong
/compareAndSwapObject
:支持 long 和对象。 -
实现 :通过 JNI 调用 CPU 的
cmpxchg
,实现无锁操作。 -
例子 :
javaunsafe.compareAndSwapInt(this, valueOffset, 5, 6);
-
-
对象与字段操作
objectFieldOffset(Field f)
:获取字段的内存偏移量。getObject(Object obj, long offset)
/putObject
:读写字段值。- 作用:绕过访问权限,支持反射和原子更新。
- 例子 :
unsafe.getInt(obj, offset)
读取私有字段。
-
线程控制
park(boolean isAbsolute, long time)
:挂起线程。unpark(Object thread)
:唤醒线程。- 作用:LockSupport 的底层实现,用于构建锁和同步工具。
-
类与实例操作
defineClass
:动态定义类。allocateInstance(Class<?> cls)
:不调用构造器创建对象。- 作用:支持框架(如 Spring 的代理生成)。
-
内存屏障
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
-
核心字段 :
javaprivate volatile int value; private static final long valueOffset; // Unsafe 计算偏移量
-
关键方法 :
-
getAndIncrement()
:自增并返回旧值,用 CAS 实现。javado { v = unsafe.getIntVolatile(this, valueOffset); } while (!unsafe.compareAndSwapInt(this, valueOffset, v, v + 1));
-
compareAndSet(expected, update)
:单次 CAS。 -
getAndAdd(int delta)
:加 delta 并返回旧值。
-
-
应用:计数器、开关控制。
-
-
记忆点:基础原子类,操作单个数字或布尔值。
2. 引用类型原子类
-
AtomicReference
-
核心字段 :
javaprivate volatile V value;
-
关键方法 :
compareAndSet(V expect, V update)
:更新引用。- 应用:无锁数据结构(如链表头节点)。
-
例子 :
javaAtomicReference<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
-
核心字段 :
javaprivate final int[] array; private static final int base; // 数组基地址
-
关键方法 :
-
getAndSet(int i, int newValue)
:更新数组元素。javalong offset = checkedByteOffset(i); while (!unsafe.compareAndSwapInt(array, offset, current, newValue));
-
-
应用:多线程操作数组元素。
-
-
记忆点:数组扩展,批量原子操作。
4. 字段更新器
-
AtomicIntegerFieldUpdater / AtomicLongFieldUpdater / AtomicReferenceFieldUpdater
-
特点:更新对象中的 volatile 字段。
-
用法 :
javaAtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "count"); updater.compareAndSet(obj, 5, 6);
-
应用:只更新字段,避免创建新对象。
-
-
记忆点:字段级原子操作,灵活但有 volatile 限制。
5. 高竞争优化类
-
LongAdder / LongAccumulator
-
特点:高并发下替代 AtomicLong,分段累加减少 CAS 冲突。
-
核心实现 :
javaprivate 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 | 分段累加,高性能 | 高并发统计 |
记忆技巧:
- 分层记忆:从简单(基本类型)到复杂(引用、数组、字段、高竞争)。
- 场景驱动:想到"计数器"就联到 AtomicInteger,"高并发统计"联到 LongAdder。
- 递进关系:Unsafe → CAS → 原子类,层层封装。
四、深入思考与总结
-
CPU 指令与 CAS
cmpxchg
是 CAS 的硬件基础,通过"比较+交换"一步完成。记住它的功能比记名字更重要。 -
Unsafe 的全面性
Unsafe 不仅是 CAS 的桥梁,还支持内存、线程、对象操作,是 Java 并发的底层支撑。
-
原子类的体系化
从基本类型到高竞争优化,原子类覆盖了各种并发需求,形成一个由简单到复杂的工具链。