为什么要使用 CAS?Unsafe、AtomicInteger 原理全揭示

365beat网页怎么打不开 2025-07-20 10:54:23 admin

多线程并发编程中,有两个永远绕不开的关键词:锁 与 无锁(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 updater =

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 并发编程的核心不只是锁,真正精妙的是在于 “用正确的原子工具,解决正确的线程问题”。

相关文章

梦幻西游:揭秘 梦幻道具为何能长久保值

为什么要朝圣,应该如何举行?

车和家的另类突围