什么是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)));
    }
相关推荐
数字化顾问12 分钟前
Flink ProcessFunction 与低层级 Join 实战手册:实时画像秒级更新系统
java·开发语言
计算机学姐20 分钟前
基于微信小程序的垃圾分类管理系统【2026最新】
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
Mr_hwt_12327 分钟前
spring boot框架中本地缓存@Cacheable原理与踩坑点详细解析
java·spring boot·后端·缓存
zl97989934 分钟前
SpringBoot-自动配置原理
java·spring boot·spring
兮动人1 小时前
Java 单元测试中的 Mockito 使用详解与实战指南
java·开发语言·单元测试
豆沙沙包?1 小时前
2025年--Lc186--64. 最小路径和(多维动态规划,矩阵)--Java版
java·矩阵·动态规划
武子康1 小时前
Java-151 深入浅出 MongoDB 索引详解 性能优化:慢查询分析 索引调优 快速定位并解决慢查询
java·开发语言·数据库·sql·mongodb·性能优化·nosql
Query*2 小时前
Java 设计模式——建造者模式:从原理到实战的极简指南
java·设计模式·建造者模式
zl9798992 小时前
SpringBoot-入门介绍
java·spring boot·spring
焰火19992 小时前
[Java]基于Redis的分布式环境下的自增编号生成器
java·后端