深入理解 CAS 原理
在多线程编程中,保证数据的安全性和一致性至关重要。CAS(Compare and Swap,比较并交换)作为一种无锁并发技术,为我们提供了高效的解决方案。本文将深入探讨 CAS 的实现原理、其在并发编程中的应用以及如何解决由其引发的 ABA 问题。
一、为什么引入 CAS?
1.1 👉synchronized 时代:悲观锁策略
传统的多线程安全性控制主要依靠 synchronized 关键字实现同步锁,其基本思想是悲观加锁:
- 加锁保证安全:假设多个线程都会修改数据,操作前先获取锁,只有持有锁的线程才能执行操作。
- 问题:
- 线程阻塞:未获得锁的线程会等待,导致 CPU 资源浪费。
- 上下文切换开销大:线程挂起与唤醒需要在用户态和内核态间切换,性能受影响。
1.2 👉volatile 时代:非阻塞同步
为了解决锁带来的阻塞问题,Java 提出了 volatile 关键字来实现非阻塞同步:
- 优势:
- 保证内存可见性和指令有序性。
- 不足:
- 不能保证原子性。例如,
i++操作包含读取、计算、写入三个步骤,依然可能出现线程安全问题。
- 不能保证原子性。例如,
1.3 CAS 机制的引入
为了在保证原子性的前提下避免线程阻塞,Java 引入了 CAS:
- 基本思想:先读取内存中的数据,再通过比较(Compare)判断是否符合预期值,符合则更新为新值(Swap)。
- 特点:基于硬件提供的原子操作指令,实现了无锁并发,提高了效率。
二、CAS 与系统内核的关系
CAS 操作依赖于底层硬件指令和操作系统内核的支持,主要体现在以下三个方面:
2.1 硬件支持
- 现代 CPU 提供专门指令(如 CMPXCHG、LL/SC)来支持 CAS 操作,确保在硬件层面上操作的原子性。
2.2 内核原子操作
- 操作系统内核对硬件指令进行封装,如 Linux 中的
atomic_compare_and_exchange等函数,使得应用程序能方便地调用 CAS。
2.3 👉内存模型
- CAS 操作需要与内存模型相结合以保证操作的可见性和有序性。在 Java 中,Java 内存模型(JMM)确保了使用 CAS 进行数据更新时的正确性。
三、CAS 的基本原理
CAS 操作涉及三个参数:
- V:需要读写的内存位置(变量地址)。
- E:预期值,即线程认为 V 应该具有的值。
- N:新值,即希望写入的值。
执行过程:
- 比较:检查内存位置 V 当前的值是否等于预期值 E。
- 交换:若相等,则将 V 更新为 N;否则不做任何修改。
- 返回结果:返回旧值,借此判断是否成功更新。
四、Java 中的 CAS 实现
在 Java 中,CAS 主要通过👉 sun.misc.Unsafe 类提供的方法实现,如:
compareAndSwapIntcompareAndSwapLongcompareAndSwapObject
下面是一个基于 Unsafe 实现的无锁计数器示例:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CASCounter {
private volatile int value;
private static final Unsafe unsafe;
private static final long valueOffset;
static {
try {
// 通过反射获取 Unsafe 实例
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
// 获取 value 字段在 CASCounter 类中的偏移量
valueOffset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
public int incrementAndGet() {
int current;
do {
current = value;
} while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1

最低0.47元/天 解锁文章
339

被折叠的 条评论
为什么被折叠?



