什么是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)));
    }
相关推荐
皮皮林5515 分钟前
设计一个多租户 SaaS 系统,如何实现租户数据隔离与资源配额控制?
java·saas
霍格沃兹软件测试开发7 分钟前
Playwright 自动化测试系列(6)| 第三阶段:测试框架集成指南:参数化测试 + 多浏览器并行执行
java·数据库·mysql·自动化
Bonnie_12151 小时前
02-netty基础-java四种IO模型
java·开发语言·nio·jetty
我不是星海1 小时前
建造者设计模式
java·开发语言
JIngJaneIL1 小时前
健身管理小程序|基于微信开发健身管理小程序的系统设计与实现(源码+数据库+文档)
java·数据库·小程序·论文·课程设计·毕设·健身管理小程序
Dcs1 小时前
Spring Framework 6.2 正式发布:开发者最值得关注的更新全览!
java
别来无恙1491 小时前
Spring Boot自动装配原理深度解析:从核心注解到实现机制
java·spring boot·后端
亲爱的非洲野猪1 小时前
Spring Cloud Gateway 电商系统实战指南:架构设计与深度优化
java·spring cloud·gateway
[听得时光枕水眠]1 小时前
Gateway
java·开发语言·gateway
m0_481147332 小时前
枚举类高级用法
java·开发语言·windows