CAS 核心机制

CAS(Compare and Swap,比较并交换)是硬件级原生支持的原子操作,是无锁编程的核心基石,也是乐观并发控制的典型实现。其核心价值在于通过硬件原子指令确保单次比较并交换操作的原子性,避免了锁竞争带来的线程阻塞与上下文切换开销,是高并发场景下实现轻量级同步的核心技术。

一、CAS 的核心定义与三要素

CAS(Compare and Swap,比较并交换)是一种基于内存地址的硬件级原子更新操作,涉及三个核心参数:V(内存位置)、E(预期值)和N(新值)。这三个参数的定义与硬件层面直接相关,是CAS操作的核心输入。具体解释如下:

  • V(Memory Location,内存位置):目标共享变量在内存中的唯一地址标识,通常是主内存中的物理存储单元或与主内存保持一致性的 CPU 缓存行。它指向要更新的变量的存储位置,而非变量值本身,是 CAS 操作的操作目标。
  • E(Expected Value,预期值):线程在执行 CAS 操作前,通过 volatile 语义或硬件原子读取指令,从内存地址 V 中获取的共享变量最新有效值。它是 CAS 操作的核心判断依据,代表当前线程 “期望” 内存地址 V 中应当存储的值。此值假设在从读取E到执行CAS期间,变量未被其他线程修改,并且缓存一致性得以保障。
  • N(New Value,新值):线程根据业务逻辑和预期值 E 计算得出的希望写入内存地址 V 的新值。仅当 CAS 操作的比较判断通过时,N 才会被 CPU 通过原子指令写入 V 所在的内存地址;否则,N 将会被丢弃,V 保持不变。

二、CAS 的核心执行逻辑

CAS 操作的核心是 “比较并交换” 的原子操作,整个执行过程由CPU通过单条原子指令完成,确保在执行期间不会发生线程切换或中断,从而保证了操作的不可拆分性和不可中断性。具体执行步骤如下:

  1. 线程通过原子读取指令获取内存地址 V 的当前值,作为预期值 E,并根据 E 计算得出新值 N;
  2. CPU 执行 CAS 原子指令:自动读取内存地址 V 中的实际值(记为 V’),并将 V’ 与线程持有的预期值 E 进行逐位比较;
  3. 判断逻辑:
    • 成功条件:若 V’ 与 E 完全相等时,表示从读取 E 到执行 CAS 期间,V 未被其他线程修改,CPU 会将新值 N 原子性地写入内存地址 V,更新成功;
    • 失败条件:若 V’ 与 E 不相等,说明期间有其他线程修改了V的值,或者缓存过期导致值不一致,CAS 操作将不执行更新操作,内存地址V保持原值V’,新值 N 被丢弃;
  4. 执行结果:CAS 操作的执行结果返回布尔值,用于告知线程操作是否成功:
    • 返回 true:表示 CAS 操作成功,内存地址 V 的值已从与 E 相等的 V’,原子性更新为新值 N;
    • 返回 false:表示 CAS 操作失败,该内存地址 V 的值仍为 V’(与 E 不相等)。此时线程可选择三种处理方式:① 自旋重试(重新读取 V’ 作为新的 E,重新计算 N 后再次执行 CAS);② 降级为锁机制(在高竞争情况下,可能会选择降级为软件锁来避免无休止的自旋);③ 直接放弃操作(根据具体业务场景,跳过此次CAS操作)。

需要注意的是,CAS 的核心在于比较与交换的不可拆分性,若拆分执行,可能会引发在比较成功后,但交换前被其他线程修改的并发安全问题。硬件级原子指令确保这一点,从而避免了该类问题。

三、CAS 的核心设计思想

CAS 的设计遵循乐观并发控制(Optimistic Concurrency Control,OCC)的核心思想,与基于悲观并发控制(Pessimistic Concurrency Control,PCC)的锁机制(如ReentrantLock、synchronized等)核心差异具体对比如下:

  • 乐观并发控制(CAS 实现)

    • 核心假设:假定在并发情况下,多线程对变量的修改冲突概率较低,因此无需加锁限制线程执行;
    • 同步策略:不阻塞任何线程的执行,线程在修改变量前不获取任何软件锁,直接执行 CAS 操作;若发生冲突(CAS 返回 false),则根据业务需求灵活选择自旋重试、降级为锁或直接放弃,不强制阻塞线程;
    • 线程状态:线程始终处于运行状态或自旋等待状态,不会因同步问题被操作系统挂起(阻塞),因此无线程上下文切换(保存线程上下文、恢复线程上下文)的开销,性能更优。
  • 悲观并发控制(synchronized、ReentrantLock 等传统锁机制)

    • 核心假设:假定多线程并发修改变量时的冲突概率较高,必须提前加锁以保证线程安全;
    • 同步策略:线程在修改变量前需获取锁,若锁已被其他线程持有,则当前线程会被阻塞挂起,进入阻塞队列,直到锁被释放后才能重新竞争锁;
    • 线程状态:线程会在 “运行 - 阻塞 - 就绪” 之间频繁切换,导致较高的上下文切换开销,尤其在高并发场景下可能成为性能瓶颈。

CAS 是乐观锁的硬件级实现,是所有软件层面乐观锁的核心基础。在我前面这篇博客里已经详细介绍了 MyBatis-Plus的乐观锁与悲观锁,感兴趣可以了解一下。常见的数据库乐观锁、分布式乐观锁,以及 MyBatis-Plus 乐观锁,其设计本质都离不开 CAS 的 “比较后更新” 核心思想。它们在 CAS 的基础上,增加了版本号、时间戳等软件层面的扩展参数旨在解决CAS可能遇到的局限性(如ABA问题),并适配更复杂的业务场景,提高并发控制的安全性和实用性。

CAS 实现的底层逻辑

为验证基于CAS实现的AtomicInteger的线程安全性,首先通过一段可运行的代码进行验证,并为后续的底层解析做铺垫。

import java.util.concurrent.atomic.AtomicInteger;

public class CasTest {

    //使用AtomicInteger定义a
    static AtomicInteger a = new AtomicInteger();
    public static void main(String[] args) {
        CasTest test = new CasTest();
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        //使用getAndIncrement函数进行自增操作
                        System.out.println(Thread.currentThread().getName() + " 自增后结果:" + a.incrementAndGet());
                        Thread.sleep(500);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }
}

这段代码创建了 5 个线程,每个线程循环 10 次执行 AtomicInteger 的原子自增操作,并打印自增后的结果。每次自增后线程休眠 500 毫秒,目的是通过线程调度的随机性验证多线程环境下 AtomicInteger 自增操作的原子性。

操作结果如下:

在这里插入图片描述

代码会输出 1 到 50 的数字,打印顺序由于线程调度的随机性会有所不同,但所有数字都会依次输出且不会重复、缺失或越界。

incrementAndGet() 方法之所以能够保证操作的原子性,其核心在于底层依赖了 CAS(Compare-And-Swap,比较并交换)机制。AtomicInteger 等原子类利用 CAS 底层原子操作工具封装出线程安全的操作方式,结合 volatile 关键字提供的内存可见性保障,同时通过自旋重试机制提升容错能力,从而无需手动加锁就能确保多线程环境下的安全访问。CAS 是支持这一方案高效运行的核心技术。

下面我们逐步解析 AtomicInteger 的核心源码,拆解 CAS 实现的完整底层逻辑。

一、 核心成员变量定义

AtomicInteger 中定义了以下核心成员变量:

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
  • Unsafe 实例:Unsafe 是 Java 提供的底层操作工具类,它是 Java 代码能够访问底层 CAS 原子指令的唯一桥梁。Unsafe 并不是 Java 标准 API 的一部分,它只对 JDK 内部类开放,普通应用程序无法直接通过 Unsafe.getUnsafe() 获取实例。

  • valueOffset:valueOffset 是通过 Unsafe.objectFieldOffset() 获取的 value 字段的内存偏移量。这个偏移量在类加载阶段由 JVM 计算,并在初始化后永久不变,确保在任何时刻都能准确定位到 value 变量的内存位置。

  • value 变量:这是一个 volatile 修饰的共享计数器。volatile 确保了多线程环境下的内存可见性和指令有序性,是 CAS 操作正常工作的前提。它与 CAS 配合,确保满足并发编程中的 原子性、可见性、和 有序性 三大特性。

二、 原子自增方法:incrementAndGet ()

incrementAndGet() 方法的核心是将自增操作委托给 Unsafe 类中的 getAndAddInt() 方法,自身仅做返回值处理,具体代码如下:

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
  • final 修饰:final 修饰符保证该方法不能被重写,从而避免子类更改该方法的行为,确保原子操作的唯一性和不可篡改性。
  • 返回值:Unsafe.getAndAddInt() 方法返回的是自增前的旧值,因此需要将其结果加 1 得到自增后的新值,这是上层 API 为了贴合业务使用习惯做的封装。

三、 核心实现:Unsafe.getAndAddInt(自旋 CAS 逻辑)

getAndAddInt() 是 CAS 机制的核心封装,它通过自旋 CAS 保证最终更新成功,并实现了 AtomicInteger 的原子性。该方法的源码如下(基于 JDK 8):

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

参数映射:

  • var1:目标对象(此处为 AtomicInteger 实例);
  • var2:目标变量在对象中的内存偏移量(此处为 valueOffset);
  • var4:自增步长(此处为 1);
  • var5:var1 对象在内存地址 var2 处的当前值,通过 volatile 语义获取最新的 value;
  • var5+var4:基于预期值计算的目标新值(此处为 var5 + 1)。

自旋 CAS 核心逻辑:

  • do-while 循环:方法先尝试执行一次 CAS 操作,若失败则进入循环重试,直到更新成功。这符合自旋 CAS 的“先尝试失败再重试”逻辑;
  • getIntVolatile () 方法:使用 volatile 语义获取 var1 对象中 var2 对应位置的值,并将其赋给 var5,volatile 确保每次读取的是最新值,避免线程从 CPU 缓存或本地内存中读取到过期的值,减少无效自旋,提高并发性能;
  • compareAndSwapInt () 方法:执行底层的 CAS 原子操作,若 var1 中偏移量 var2 对应的变量值与预期值 var5 相等(按二进制逐位比较),则将其更新为 var5 + var4。返回值为 true 表示更新成功,false 表示更新失败(即变量已经被其他线程修改);
    • 返回旧值:方法返回更新前的旧值 var5,如果 CAS 操作被其他线程干扰而失败,返回的是本次 CAS 操作尝试的旧值 var5;这是 Unsafe 类原子方法的统一设计规范,便于上层方法灵活处理。

四、 底层桥梁:Unsafe 类(Java 与硬件指令的交互入口)

AtomicInteger 的 CAS 操作最终依赖 sun.misc.Unsafe 类实现,该类是 Java 提供的底层硬件操作接口,是 Java 与底层硬件指令(如 C/C++ 原生代码)之间的桥梁,支持 CAS 机制的高效执行。

CAS 核心方法:

public final class Unsafe {
    // 修改 int 类型变量的 CAS 操作(核心 native 方法)
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    
    // 对应其他类型的 CAS 方法(核心逻辑一致,仅类型不同)
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var5);
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
}

这些方法是实现变量无锁原子更新的核心,AtomicInteger、AtomicLong 等原子类的底层原子操作均依赖这类方法。它们通过硬件级指令保证 CAS 的原子性,从而实现无锁线程安全。

  • 参数介绍:
    var1:CAS 操作的目标对象(对应 AtomicInteger 实例 a);
    var2:目标变量在对象中的内存偏移量(有效地址,对应 valueOffset);
    var4:线程预期的变量当前值(对应 getAndAddInt 中的 var5,即 CAS 三要素中的 E(预期值));
    var5:线程希望更新的新值(对应前文getAndAddInt中的var5 + var4(步长计算结果),即 CAS 三要素中的 N(新值))。
  • native 方法的底层实现:该方法无 Java 层面实现,底层由 HotSpot 虚拟机的 C/C++ 代码实现,映射到 CPU 的原生原子指令,执行过程不可中断,不受操作系统线程调度影响;
  • CPU 原子指令映射:在 x86 架构的 CPU 中,该方法最终映射到 LOCK CMPXCHG 指令,LOCK前缀用于锁定 CPU 缓存行或系统总线,保证指令执行的原子性;
  • 返回值语义:方法返回布尔值仅表示是否更新成功,不返回变量的最新值,这是 CAS 操作的标准返回结果,便于上层实现自旋重试逻辑。

Unsafe 类的核心常用方法分类

sun.misc.Unsafe 提供了多种底层操作功能,其中一些关键方法如下:

  • 内存偏移量定位方法:
    long objectFieldOffset(Field field):获取指定字段在对象内存中的偏移量。
    int arrayBaseOffset(Class arrayClass):获取数组类第一个元素的内存偏移量。
    int arrayIndexScale(Class arrayClass):获取数组类中单个元素的字节数。
  • CAS 原子操作方法:
    boolean compareAndSwapInt(Object obj, long offset, int expect, int update):int 类型 CAS 操作(AtomicInteger 核心)。
    boolean compareAndSwapLong(Object obj, long offset, long expect, long update):long 类型 CAS 操作(AtomicLong 核心)。
    boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update):引用类型 CAS 操作(AtomicReference 核心)。
  • Volatile 语义读写方法
    long getLongVolatile(Object obj, long offset):以 volatile 语义获取对象字段当前值。
    void putLongVolatile(Object obj, long offset, long value):以 volatile 语义写入字段值。
  • 有序写入方法
    void putOrderedLong(Object obj, long offset, long value):保证有序性但不保证实时可见性,性能高于 putLongVolatile。
  • 原子获取并更新方法
    long getAndSetLong(Object obj, long offset, long update):原子性地获取目标字段当前值并设置为新值。

Unsafe 的访问限制

由于 Unsafe 类操作权限强大且不受 JDK 内存模型的严格约束,访问需要存在严格限制。
以下是绕过这些限制的方法以及相关示例代码:

import sun.misc.Unsafe;

public class TestUnSafe {
    //获取Unsafe的实例(2.2.1)
    static final Unsafe unsafe = Unsafe.getUnsafe();
    //记录变量state在类TestUnSafe中的偏移值(2.2.2)
    static final long stateOffset;
    //变量(2.2.3)
    private volatile long state = 0;

    static {
        try {
            //获取state变量在类TestUnSafe中的偏移值(2.2.4)
            stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (Exception ex) {
            System.out.println(ex.getLocalizedMessage());
            throw new Error(ex);
        }
    }

    public static void main(String[] args) {
        //创建实例,并且设置state值为1(2.2.5)
        TestUnSafe test = new TestUnSafe();
        // (2.2.6)
        Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
        System.out.println(sucess);

    }
}

Unsafe 类的构造方法是私有的,且 JVM 做了安全校验,普通开发者无法直接通过 new Unsafe() 或 Unsafe.getUnsafe() 获取实例,调用时会抛出 SecurityException,因此,开发者必须通过反射来访问 Unsafe 实例。

代码(2.2.1) 获取 Unsafe 的一个实例。代码(2.2.3) 定义了一个变量 state,并初始化为 0。代码(2.2.4) 使用 unsafe.objectFieldOffset() 获取 TestUnSafe 类中 state 变量在内存中的偏移地址,并将其存储在 stateOffset 变量中。代码(2.2.6) 使用 compareAndSwapInt() 执行 CAS 操作,将 state 变量从 0 更新为 1。期望输出为 true,但实际执行时输出了不同的结果,如下图所示:

在这里插入图片描述

抛出 java.lang.SecurityException 异常是由于 Unsafe.getUnsafe() 方法被 JVM 严格的安全机制保护,普通用户不能直接访问。

Unsafe.getUnsafe() 的访问校验与绕过方式

Unsafe.getUnsafe() 方法的源码如下:

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

校验逻辑:
@CallerSensitive 注解用于确保获取调用者的类信息,防止通过反射绕过安全检查;
getUnsafe() 方法会检查调用者的类加载器是否为启动类加载器(Bootstrap ClassLoader),只有 JDK 内部核心类(如 java.util.concurrent 包下的类)才能通过校验;
这种限制的目的是防止用户自定义类通过 Unsafe 类破坏 JVM 的内存模型和稳定性。

绕过访问限制的唯一可行方式:
绕过 Unsafe 访问限制的唯一可行方式是通过反射访问 Unsafe 类的静态成员 theUnsafe。这样可以绕过 getUnsafe() 的访问限制,允许开发者自定义使用 Unsafe。

以下是通过反射绕过限制的示例代码:

public class TxUnsafe {
    
    static final Unsafe unsafe;
    
    static final long offSet;
    
    volatile  int state = 0;
    
    static{
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            offSet = unsafe.objectFieldOffset(TxUnsafe.class.getDeclaredField("state"));
        } catch (Exception e) {
            e.printStackTrace();
            throw new Error();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TxUnsafe tu = new TxUnsafe();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    increment(tu, offSet);
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(tu.state);
    }

    public static void increment(Object obj, long offSet){
        int oldVal = 0;
        do{
             oldVal = unsafe.getIntVolatile(obj, offSet);
        }while (!unsafe.compareAndSwapInt(obj, offSet, oldVal, oldVal+1));
    }
}

输出结果:

在这里插入图片描述

核心解析:

  • 反射获取 Unsafe:通过反射访问 Unsafe 类的静态成员 theUnsafe 来绕过访问限制。theUnsafe 是 Unsafe 类的一个静态字段,存储了该类的唯一实例,通过反射,可以绕过 Java 安全模型的限制,间接访问 Unsafe 实例;
  • 自旋 CAS 逻辑:使用自旋 CAS 通过 unsafe.compareAndSwapInt() 方法实现,通过 do-while 循环重试的方式进行 CAS 操作,直到操作成功;
  • volatile 语义支持:通过 getIntVolatile() 获取变量的最新值,减少无效自旋,提升并发性能;
  • 与 AtomicInteger 同源:这段代码通过 Unsafe 实现了类似于 AtomicInteger 中 getAndAddInt() 的原子操作。AtomicInteger 使用 CAS 来保证操作的原子性,并通过 getAndAddInt() 等方法提供原子性的加法操作。

注意事项:
该方案仅用于学习 CAS 原理,生产环境中推荐使用 JDK 封装好的 Atomic 系列类,以避免因底层操作失误导致内存泄露或数据错乱。
反射关闭访问权限检查(setAccessible(true))会破坏 Java 的访问控制机制,需谨慎使用;
高并发高冲突场景下,自旋 CAS 会导致大量无效重试,占用 CPU 资源,此时推荐使用 ReentrantLock 等悲观锁。

CAS 的局限性及解决方案

ABA 问题是 CAS(Compare and Swap)机制的核心局限性之一,也是乐观并发控制中需要重点规避的潜在风险。

ABA 问题是什么?
当线程 A 读取到共享变量的值为 A(作为 CAS 操作的预期值 E)后,线程 B 先将该变量的值修改为 B,随后又将其修改回 A;当线程 A 后续执行 CAS 操作时,会发现变量当前值仍为 A(与预期值一致),误以为期间没有其他线程修改过该变量,从而成功执行更新操作,但实际上变量已经经历了 A→B→A 的两次修改,这就是 CAS 机制的 ABA 问题。

下面的代码复现了 ABA 问题的核心场景,通过两个线程模拟 A→B→A 的修改过程,验证普通 AtomicInteger 无法感知该变化:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ABAAtomic {

    private static AtomicInteger atomicInt = new AtomicInteger(100);

    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                System.out.println("thread intT1:" + atomicInt.get());
                atomicInt.compareAndSet(101, 100);
                System.out.println("thread intT1:" + atomicInt.get());
            }
        });

        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println("thread intT2:" + atomicInt.get() + ",c3 is:" + c3);        //true
            }
        });

        intT1.start();
        intT2.start();
    }
}

在这里插入图片描述

ABA 问题的核心解析
表面结果:线程 T2 的 CAS 操作成功执行,从值的角度看没有任何异常。
本质问题:线程 T2 误以为变量从读取到执行 CAS 期间从未被修改,但实际上变量已经被线程 T1 修改了两次(100→101→100),普通 CAS 仅能完成值的比对,无法感知变量的修改历史。

潜在风险场景:
简单计数器场景(如 AtomicInteger 自增):ABA 问题无实际危害,最终计数器结果仍正确。
带状态的数据结构场景(如链表、栈、队列):ABA 问题可能引发严重错误。例如:链表节点 A 被线程 T1 删除(修改为 B),随后又被线程 T1 重新插入(修改回 A),线程 T2 此时执行节点 A 的 CAS 操作,可能导致链表结构错乱、节点复用异常等问题。

ABA 问题的核心解决方案:
JDK 提供了 java.util.concurrent.atomic.AtomicStampedReference 类,专门用于解决 ABA 问题。该类封装了引用值(Reference)+ 版本戳(Stamp)双维度比对替代 CAS 单一值比对。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAAtomic1 {

    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);

    public static void main(String[] args) {
        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                System.out.println("thread refT1:" + atomicStampedRef.getReference());
                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                System.out.println("thread refT1:" + atomicStampedRef.getReference());
            }
        });

        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                System.out.println("before sleep : stamp = " + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                System.out.println("thread refT2:" + atomicStampedRef.getReference() + ",c3 is " + c3);
            }
        });
        refT1.start();
        refT2.start();
    }
}

在这里插入图片描述

AtomicStampedReference<Integer>(100, 0) 构造方法有两个参数,第一个参数是引用类型的初始值,第二个参数是 int 类型的初始版本戳。
将变量值与修改标记(版本戳)绑定,打破 CAS 单一值比对的局限,让变量的修改历史可追溯,这是规避 ABA 问题的核心前提。

这段代码首先初始化了一个封装 Integer类型引用值100和初始版本戳0的AtomicStampedReference对象,随后主线程启动两个线程refT1和refT2,其中线程refT2优先执行,先获取当前版本戳0并打印,接着进入2秒休眠;线程refT1启动后先进入1秒休眠(确保refT2已获取初始版本戳),休眠结束后依次执行两次双维度CAS操作,第一次将引用值从100修改为101、版本戳从0自增为1并打印修改后的引用值,第二次再将引用值从101修改回100、版本戳从1自增为2并打印修改后的引用值,完成「A→B→A」的ABA式修改闭环;待refT2完成2秒休眠后,先获取并打印当前已被refT1修改后的版本戳(通常为1或2),随后以初始获取的版本戳0和引用值100为预期,尝试执行CAS操作将引用值改为101、版本戳改为1,最终由于此时引用值虽回到100,但版本戳与预期的0不匹配,导致CAS操作返回false且未执行任何更新,最后打印出引用值仍为100且CAS操作失败的结果,通过「引用值+版本戳」的双维度原子比对,成功规避了CAS机制的ABA问题。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐