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

多线程并发编程中,有两个永远绕不开的关键词:无锁(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<User, String> 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 并发编程的核心不只是锁,真正精妙的是在于 “用正确的原子工具,解决正确的线程问题”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小健学 Java

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值