CAS (CompareAndSwap) 底层基本原理分析和 ABA问题

CAS(CompareAndSwap)是一种原子性操作,用于在更新变量时检查其值是否未被其他线程修改。它通过Unsafe类在底层实现,利用CPU的cmpxchg指令保证原子性。然而,CAS存在ABA问题,即值在比较和替换之间可能经历了变化再恢复的情况。为解决此问题,JDK提供AtomicStampedReference,通过附加版本号来确保操作的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CAS机制

CAS——CompareAndSwap:比较并替换
作用是进行计算的时候判断当前值是否满足预期,如果满足则更新为新值,保证整个过程具备原子性。通过内存中的值,逻辑值和要更改的值进行比较替换,通过自旋的方式在操作内存的值的时候通过内存的值和逻辑值进行比较,如果一致,则替换更改(这一步原子操作)。

代码分析:

JDK中为了方便开发正操作,已经实现了很多原子性操作的类,这些类底层就是通过CAS控制原子操作的,比如AtomicInteger,通过AtomicInteger 提供的API就可以验证CAS的简单执行原理。在AtomicInteger API中可以看到具体实现是通过sun.misc包中的Unsafe类实现的。后面会简单的说明,如下代用来验证CAS的简单执行过程和思想。

public class Test {
    public static void main(String[] args) {
        AtomicInteger test = new AtomicInteger();
        System.out.println(test.get());   // 初始化的 值为 0 
        /*
         //该方法就是根据预期来更新值得,底层是调用了Unsafe类的 compareAndSwapInt
         //expect 预期值, update 更新的值
         public final boolean compareAndSet(int expect, int update) {
         	 // 最后调用的是Unsafe类中的compareAndSwap 方法
		     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
		 }
         */
        boolean b = test.compareAndSet(2, 1);  // 传入一个预期值2  但实际值是0
        System.out.println(b);   // false   实际值0 不满足预期值2 即设置不成功
        System.out.println(test.get());  // 0  
        b = test.compareAndSet(0, 10);   // 预期值0   实际值0 
        System.out.println(b);        // true
        System.out.println(test.get());  // 10  满足预期
    }
}
Unsafe 类

Unsafe是存在于sun.misc包中的一个类,该类的作用是使Java拥有想C语言一样直接操作内存空间的能力,该类使用了单利模式,需要通过一个静态方法来获取,但是又做了限制,如果是普通调用,会抛出一个SecurityException的异常,只允许jdk 加载核心类的类加载器(Bootstrap)加载的类才可以调用。该类提供了很多直接操作内存的方法。

// Unsafe提供了许多可以直接操作内存的一些API
// 如分配指定内存大小,释放内存,给指定内存设值等。
public native long allocateMemory(long bytes);
public native long reallocateMemory(long address, long bytes);
public native void freeMemory(long address);
public native void setMemory(Object o, long offset, long bytes, byte value);
public native void putAddress(long address, long x);
public native long getAddress(long address);
public native void putLong(long address, long x);
public native long getLong(long address);
public native byte getByte(long address);
public native void putByte(long address, byte x);
public native int pageSize();

Unsafe从名称上就可以看到该类是非安全的,JAVA官方也不建议直接只用该类,所以对该类做了很多限制,禁止开发者直接在Java中直接调用Unsafe 相关的代码,包括方法修饰符都是native,以及只允许JDK加载核心类加载器加载的调用。

// Unsafe.class
@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}
// VM.class
public static boolean isSystemDomainLoader(ClassLoader var0) {
    return var0 == null;
}

在自己代码中执行 “Unsafe unsafe = Unsafe.getUnsafe();” 语句会抛异常,这也印证了上面说的。debug如下
在这里插入图片描述

简单了解Unsafe底层实现

Unsafe.java 的底层具体实现是通过C++ 实现的,unsafe.cpp 的方法如下:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(
  JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

不难看出,在这些代码中最有价值的一行代码就是 return (jint)(Atomic::cmpxchg(x, addr, e)) == e;关于该方法具体细节没有细究,这行代码的大概意思就是 (Atomic::cmpxchg(x, addr, e)) 的返回值和 e作比较,相等就返回true。
如下是 底层cmpxchg 的具体方法

inline jint     Atomic::cmpxchg(
    jint     exchange_value, 
    volatile jint*     dest, 
    jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile ("cmp $0, " "%4" "; je 1f; lock; 1: " "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

通过参考别的文章和资料,大概了解到 cmpxchg 方法到CPU层面的具体实现是通过一条 cmpxchg 指令,关于Linux内核的具体实现可以参考https://blog.csdn.net/zdy0_2004/article/details/48013829
https://blog.csdn.net/isea533/article/details/80301535

CAS ABA问题

什么事ABA?

由于CAS的整个过程是在操作值得时候检查时不时和原来的有出入,但是这样很难避免一个问题,就是调用开始到具体操作这个过程中有没有变化过,就比如如果一个值原来是A,在开始操作之前由原来的A变成B,再变回A的过程。就是说在具体操作的时候,这个A已经不是原来的A了,这样就不具备原则性。
网上看到过这样一个例子,虽然很俗,但是很形象,说你和你的女朋友分手了,分手后和别的男人好了,然后和那个男人分手后又来找你,这个时候你女朋友还是原来的女朋友么/呲牙=,=。

如何解决这个问题?

解决思路:操作的时候通过加版本号来避免ABA问题。
从JDK5开始,提供了一个AtomicStampedReference 来解决这个问题,通过加一个版本号来解决这个问题,操作一次,版本号更新一次。
示例代码:

public class ABADemo {
    private static AtomicInteger atomicInteger = new AtomicInteger(1);
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1, 0);

    // 常规原子操作-----不考虑ABA问题
    public static void testAtomicInteger(){

        Thread t1 = new Thread(()->{
            atomicInteger.compareAndSet(1, 2);
            atomicInteger.compareAndSet(2, 1);
        });
        Thread t2 = new Thread(()->{
            try {
                t1.join();
            } catch (Exception e) {
            }
            boolean success = atomicInteger.compareAndSet(1, 2);
            System.out.println(success);
        });
        t1.start();
        t2.start();
    }

    // 考虑ABA问题----通过Reference 解决
    public static void testAtomicStampedReference(){
        Thread t1 = new Thread(()->{
            atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
        });
        Thread t2 = new Thread(()->{
            try {
                t1.join();
            } catch (InterruptedException e) {
            }
            int stamp = atomicStampedReference.getStamp();
            System.out.println(stamp);    // 2
            System.out.println(atomicStampedReference.getReference());  //1
            Integer expectStemp = stamp;   // 2
            // expectStemp = 1;
            boolean success = atomicStampedReference.compareAndSet(1, 2, expectStemp, stamp+1 );
            System.out.println(success);
        });
        t1.start();
        t2.start();

    }

    public static void main(String[] args) throws Exception {
        testAtomicInteger();    // true
        // 如果将上面expectStemp 的值设置成2的,则输出true,设置成1 则输出false
        // 这说明通过版本号可以解决ABA问题
        testAtomicStampedReference();
    }
}
### CAS (Compare-And-Swap)底层实现原理 CAS 是一种基于硬件支持的原子操作,它允许在多线程环境中执行无锁的数据结构操作。其核心在于通过比较内存位置中的当前值与期望值来决定是否进行替换。如果匹配成功,则将新的值写入该位置;如果不匹配,则不会修改原有值。 #### 工作流程 CAS 操作通常由硬件级别的指令集提供支持,在现代计算机体系架构中,这种功能已经被集成到处理器内部。具体来说,CAS 使用了一种称为 **LL/SC(Load-Linked / Store-Conditional)** 或者直接内置的 `CMPXCHG` 类型指令[^5] 来完成这一过程: 1. **加载链接阶段**: 处理器会先从指定的内存地址读取旧值,并将其标记为“已锁定”。这意味着其他线程在此期间尝试访问同一内存区域可能会失败或者等待。 2. **条件存储阶段**: 接下来,CPU 将检查之前获取的值是否仍然保持不变。如果是的话,则把新值存放到目标位置并将状态设置为解锁;如果不是,则放弃此次更改请求并返回错误标志给调用方知道发生了竞争情况需要重新尝试整个动作直到成功为止。 #### Java 中的实现细节 在 Java 虚拟机层面,CAS 功能主要借助于 Unsafe 类暴露出来的几个关键方法得以体现出来比如 compareAndSwapInt compareAndSwapObject 等等[^5] 。 这些 API 提供了一个接口让开发者能够方便地利用平台特定优化后的机器码来进行高效的同步控制而不必担心跨平台兼容性问题因为 JVM 自己负责屏蔽掉不同操作系统之间的差异部分使得应用程序员只需关注逻辑编写即可获得高性能表现同时还能享受到安全性稳定性方面的保障措施如防止越界访问等问题发生等等优点所在之处显而易见值得我们深入学习研究下去不断进步成长成为更好的工程师! ```java // 示例代码展示如何使用 Unsafe 类实现 CAS 操作 import sun.misc.Unsafe; public class CasExample { private static final Unsafe UNSAFE; private static final long VALUE_OFFSET; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); UNSAFE = (Unsafe)field.get(null); VALUE_OFFSET = UNSAFE.objectFieldOffset(CasExample.class.getDeclaredField("value")); } catch(Exception e){ throw new RuntimeException(e); } } volatile int value; public boolean cas(int expect, int update){ return UNSAFE.compareAndSwapInt(this, VALUE_OFFSET, expect, update); } } ``` 此段代码定义了一个简单的类 `CasExample`, 它包含了两个重要成员变量: 一个是用来保存共享资源数值的状态字段 `volatile int value`; 另外则是通过反射机制取得实例化对象之后才能使用的工具类 `sun.misc.Unsafe`. 此处展示了最基本的 CAS 方法签名形式及其工作方式即传入三个参数分别是待检验的目标实体引用、预计看到的老版本数据副本以及准备提交上去的新候选方案内容最后依据实际情况作出相应调整从而达到最终目的——确保每一次更新都是建立在过去某个确切时刻所观察得到的基础上面这样就可以有效规避传统加锁手段所带来的诸多弊端诸如死锁风险增加上下文切换频率降低整体吞吐量水平下降等一系列负面效应影响用户体验质量等方面均有所改善提升效果显著可见一斑! ### 性能考量与局限性分析 虽然 CAS 技术带来了许多好处,但它并非万能解决方案。例如,在极端情况下频繁发生的争用现象可能导致所谓的 “忙等待” 效应进而消耗大量 CPU 时间片造成不必要的浪费[^2]; 同时还存在 ABA 问题如果没有额外辅助机制配合则无法区分两次连续相同值之间是否存在中间变化过程因此必须谨慎选用适当场合下合理运用才不至于适得其反事倍功半徒劳无益反而增加了开发维护成本降低了软件系统的健壮程度可靠性指标得分等情况的发生几率减少至最低限度以内才是明智之举正确之道也.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值