什么是CAS机制

CAS机制

CAS是Compare-And-Swap的缩写,即比较和交换,比较和交换是原子操作,通过CPU指令层面保证了其原子性。CAS通过将预期的值和实际的值进行比较,如果预期的值和实际的值一样,则认为实际值没有发生变化,此时可以将实际值设置为新值。下面通过一段Java代码来演示一下CAS的使用:

java 复制代码
import sun.misc.Unsafe;
import java.lang.reflect.Field;

class Counter {

    private volatile int i = 0;

    /**
     * Unsafe 是 JDK封装的一个内部使用类,需要使用反射的方式来创建其对象
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    }

    void casSetValue(int expected, int newValue) throws NoSuchFieldException, IllegalAccessException {
        Unsafe unsafe = getUnsafe();
        long offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("i"));
        unsafe.compareAndSwapInt(this, offset, expected, newValue);
    }

    int getValue() {
        return i;
    }
}

public class CasDemo01 {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Counter counter = new Counter();
        // 这里预期值是2,但实际值是0,新的值会设置失败
        counter.casSetValue(2, 1);
        // 输出的结果为: 0
        System.out.println(counter.getValue());
        // 这里预期值是0,实际值也是0,新的值设置成功
        counter.casSetValue(0, 2);
         // 输出的结果为: 2
        System.out.println(counter.getValue());
    }

}

上面演示了Java代码中如何使用CAS机制,Java提供了Unsafe类,其中有提供三个实现CAS机制的核心方法,分别对不同类型的变量进行修改。

java 复制代码
//用于整型变量的原子操作
boolean compareAndSwapInt(Object o, long offset, int expected, int x)
//用于长整型变量的原子操作
boolean compareAndSwapLong(Object o, long offset, long expected, long x)
//用于对象引用的原子操作,不涉及对象的属性
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)

这些方法都调用了Java本地方法,由JVM提供底层实现。现代的CPU架构中,这些操作通常会被编译为特定的CPU指令。在x86架构中,对应的是CMPXCHG,它可以原子性的完成比较和交换操作,确保在多线程环境下的线程安全性。

这种硬件级别的支持使得CAS非常高效:

  • 由CAS实现的同步,避免了synchronized锁带来的上下文切换
  • 实现了真正的无锁编程
  • 在竞争不激烈的情况下,性能显著优于传统的同步机制(因为一旦竞争过于激烈,会存在CAS一直失败的情况,反而性能有限)。

ABA问题

如果有一个整型值X,一开始实际值是1,然后被修改为2,最后又恢复成1,这个时候我们使用compareAndSwapInt(Object o, long offset, int 1, int 3)来修改这个X的时候,虽然修改可以成功,看起来没什么问题,但是CAS无法感知这中间的变化,可能导致业务逻辑问题。

要解决这个问题,需要为要修改的值提供一个版本号。下面是Java的concurrent包中的一个实现:

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaProblem {
    public static void main(String[] args) {
        // 初始化版本号号是0
        AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
        Integer stamp = ref.getStamp();
        // 输出 0
        System.out.println("版本号是:" + stamp);
        ref.compareAndSet(1, 3, stamp, stamp + 1);
        // 输出 1
        System.out.println("新的版本号是:" + ref.getStamp());
    }
}

查看源码发现,AtomicStampedReference定义了一个私有内部类Pair,使用Pair存储值和版本号。在 ref.compareAndSet中创建一个新的Pair对象,并通过compareAndSwapObject()方法更新AtomicStampedReference中对Pair对象的引用,可以起到同时原子更新两个变量的效果(值和版本号)。

java 复制代码
 private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
}

ref.compareAndSet的源码如下

java 复制代码
// Pair.of 创建新的Pair对象
// casPair方法,类似上面的compareAndSwapObject方法,原子化更新对象的引用
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
相关推荐
iナナ2 分钟前
初识JVM
java·jvm
m0_5704664112 分钟前
代码随想录算法训练营第二十八天 | 买卖股票的最佳实际、跳跃游戏、K次取反后最大化的数组和
java·开发语言·算法
ST.J1 小时前
swing笔记
java·笔记
菩提树下的凡夫1 小时前
瑞芯微RV1126目标识别算法Yolov8的部署应用
java·算法·yolo
爱隐身的官人1 小时前
新后端漏洞(上)- Java RMI Registry反序列化漏洞
java·反序列化漏洞
叫我阿柒啊1 小时前
从Java全栈到前端框架:一次真实的面试对话与技术解析
java·javascript·typescript·vue·springboot·react·前端开发
晚安里2 小时前
Spring 框架(IoC、AOP、Spring Boot) 的必会知识点汇总
java·spring boot·spring
爱隐身的官人2 小时前
新后端漏洞(上)- Aapache Tomcat AJP 文件包含漏洞(CVE-2020-1938)
java·tomcat·ajp
@CLoudbays_Martin112 小时前
为什么动态视频业务内容不可以被CDN静态缓存?
java·运维·服务器·javascript·网络·python·php
四谎真好看2 小时前
Java 学习笔记(进阶篇2)
java·笔记·学习