多线程并发编程中,有两个永远绕不开的关键词:锁 与 无锁(Lock-Free)。
而实现无锁操作的核心就是 —— CAS。
本篇文章将带你深入理解 CAS 背后的原理,解构 AtomicInteger 与 Unsafe 的底层实现逻辑。
一、CAS 是什么?
CAS(Compare-And-Swap) 是一种原子操作指令。
它的基本思想是:
当你想修改某个值时,先比较这个值是否是你预期的旧值,如果是,就将其改为新值;否则说明有其他线程修改了,再重试。
CAS 涉及三元组:
CAS(V, E, N)V:要更新的变量(内存值)E:预期值(Expected)N:新值(New)
若 V == E,则将 V 设为 N;否则不修改,表示失败。
二、为什么需要 CAS?
传统的并发控制往往依赖 synchronized、Lock 等机制,依靠互斥锁保障线程安全。
缺点:
上锁、解锁存在性能开销
线程阻塞/唤醒涉及上下文切换
竞争激烈时可能发生线程“饥饿”
而 CAS 是一种乐观锁机制,避免加锁,通过 自旋重试 + 原子更新 实现线程安全。
三、AtomicInteger 的底层实现
以 AtomicInteger.incrementAndGet() 为例,核心逻辑:
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
背后使用了 Unsafe 类的方法:
public final int getAndAddInt(Object o, long offset, int delta) {
int prev, next;
do {
prev = getIntVolatile(o, offset);
next = prev + delta;
} while (!compareAndSwapInt(o, offset, prev, next));
return prev;
}
解读:
首先读取当前值(通过偏移量 + 内存地址)
然后尝试用 CAS 修改它
如果失败(其他线程改过),就自旋重试
这就是经典的“读-改-写”+CAS循环模式。
四、Unsafe 是什么?
sun.misc.Unsafe 是 Java 提供的一个底层类,可以进行:
直接操作内存
实现 CAS 原子操作
实现线程挂起与恢复
构建高性能数据结构
虽然是 非公开 API,但 JDK 内部的 java.util.concurrent 包广泛使用它。
使用方式:
Unsafe unsafe = ... // 通过反射或 VarHandle 获取
long offset = unsafe.objectFieldOffset(Field);
unsafe.compareAndSwapInt(obj, offset, expect, update);
五、CAS 有哪些问题?
虽然性能高,但 CAS 并非万能:
1. ABA 问题
线程A读取到变量是A,其他线程改为B再改回A,CAS会误认为变量未变。
解决方式:使用 版本号机制(如 AtomicStampedReference)
2. 自旋过多,浪费 CPU
在竞争激烈的环境中,CAS 可能不断失败,占用大量 CPU。
解决方式:合理控制自旋次数,或降级使用锁
3. 只能更新一个变量
CAS 只能保证单变量原子性,不能一次更新多个变量。
解决方式:使用 AtomicReference + 自定义数据结构
六、CAS 与锁的对比
特性
CAS 无锁方案
synchronized/Lock
性能
高(低冲突场景)
低(需上下文切换)
是否阻塞
否
是
是否公平
否
可选(如 ReentrantLock)
编程复杂度
高
低
所以:
低冲突 + 高性能场景适合 CAS,高冲突或有顺序需求时仍需锁机制补位。
七、为什么 LongAdder 比 AtomicLong 更快?
在高并发场景下,AtomicLong 性能瓶颈明显 —— 它依赖 CAS 保证原子性,但多个线程同时竞争一个变量的更新会导致大量失败、自旋,最终吞吐量降低。
为解决这个问题,JDK 引入了 LongAdder。
原理解读:
LongAdder 并不是单纯的一个变量,而是内部维护了一个 分段累加数组(Cell 数组):
public class LongAdder extends Striped64 {
public void increment() {
add(1);
}
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
// 尝试直接累加,如果失败则进入分段累加逻辑
if ((cs = cells) != null || !casBase(b = base, b + x)) {
// 自适应扩容到多个 Cell,均匀写入
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
}
工作机制:
多线程竞争时,LongAdder 会分配多个 Cell,每个线程只争用一个 Cell,避免热点冲突。
最终统计结果时,再将所有 Cell 加总。
优势:
场景
AtomicLong
LongAdder
单线程更新
快(直接 CAS)
稍慢(有分段)
高并发累加
易竞争、慢
分段累加、快
读取性能
快
慢(需要加总)
✅ 总结: 高并发统计类场景(如 QPS、访问计数器)推荐使用 LongAdder。
八、AtomicReferenceFieldUpdater 精准原子更新字段
AtomicReferenceFieldUpdater 提供了一种对对象中 某个字段 进行 CAS 操作的能力。
相比 AtomicReference 操作整个对象,Updater 更加精细、节省内存。
示例代码:
class User {
volatile String status;
}
AtomicReferenceFieldUpdater
AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "status");
User user = new User();
user.status = "INIT";
boolean success = updater.compareAndSet(user, "INIT", "FINISHED");
应用场景:
多线程环境下修改对象某一字段(而非整个对象)
JUC 中的 FutureTask、LinkedTransferQueue 等大量使用该方式
注意事项:
字段必须是 public volatile
不能是 static 修饰
使用时需谨慎,反射和类型检查开销存在
九、VarHandle:Java 9 的现代内存操作工具
在 Java 9 之后,VarHandle 被设计为 替代 Unsafe 与 FieldUpdater 的官方方案。
它不仅支持原子操作,还支持内存屏障、可见性控制等。
获取方式:
public class Example {
volatile int count;
private static final VarHandle COUNT;
static {
try {
COUNT = MethodHandles.lookup()
.findVarHandle(Example.class, "count", int.class);
} catch (Exception e) {
throw new Error(e);
}
}
public void increment() {
COUNT.getAndAdd(this, 1);
}
}
相比 Unsafe:
特性
Unsafe
VarHandle
安全性
非公开、易误用
官方、安全封装
使用复杂度
高(偏底层)
中(有统一接口)
兼容性与可移植性
差
优秀
VarHandle 能做什么?
读取/写入/原子更新字段值
控制内存语义(Acquire/Release/Volatile/Plain)
替代 FieldUpdater 和部分 Unsafe 用法
✅ 推荐: 在 JDK 9+ 的项目中,建议逐步以 VarHandle 替代 AtomicXxxFieldUpdater 和 Unsafe 进行字段原子操作。
十、总结与实践建议
工具
使用场景
AtomicInteger / AtomicLong
简单并发数值操作
LongAdder / LongAccumulator
高频统计性能优化
AtomicReferenceFieldUpdater
更新对象内部字段,内存占用更少
VarHandle(JDK 9+)
更安全更灵活的原子操作,建议逐步替代 Unsafe
Unsafe
高性能 + 高风险,建议只用于底层框架中
Java 并发编程的核心不只是锁,真正精妙的是在于 “用正确的原子工具,解决正确的线程问题”。