CAS的ABA问题如何解决

177 阅读3分钟

CAS(Compare And Swap)是一种无锁的同步机制,它尝试原子地读取某个内存位置的值,比较该值是否符合预期,如果符合预期,则执行设置新值的操作,否则重新读取该内存位置的值,直到符合预期为止。但是,CAS存在ABA问题,即一个原本被认为已经不存在的值被其他线程修改后又变回该原来的值,从而导致CAS操作无法正确地识别出该内存位置的变化。

为了解决这个问题,可以增加一个版本号,每次变化都将版本号+1,这样可以避免因为ABA的问题导致出现错误的数据操作。常用的是AtomicStampedReference类,它的更新操作需要传入期望值、新值和期望的版本号和新的版本号,只有这些全部匹配时,才执行更新操作。

具体的实现思路是,在引用实例中除了需要存储对象的引用外,还需要存储一个版本号,然后在更新时,比较期望版本号与当前版本号是否一致。如果找到了期望版本号的值,则将操作成功,否则循环读取并修改,直到成功。

例如,一个线程想要将一个变量从A修改成B,然后再修改回A。如果只使用CAS,另一个线程可能会将其在A-》B的修改完成后将其再改回A,导致CAS不能感知到这一变化,误认为B已经被放置在位置上。但是加上版本号后,每次修改都将版本号+1,如果第一线程的ABA操作之间出现了中间变化,则版本号也会增加,第二线程再回到原来的版本时,版本号也会不同,CAS操作就能检测到变化,从而保证操作的正确性。

在下代码中,使用AtomicStampedReference类定义了一个初始值为100的原子引用,同时设置了一个版本号,即初始版本号为0。在线程1中实现了ABA操作,即将原子引用从100修改为101,再修改回100。为了让这个操作发生ABA问题,线程2在线程1执行操作的同时将原子引用修改为102,然后再修改回100。最后,输出原子引用的值,可以看到最终值为102,没有出现ABA问题。

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

public class CASABAProblem { // 使用AtomicStampedReference来解决ABA问题 private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference<>(100, 0);

public static void main(String[] args) throws InterruptedException {
    // 线程1执行ABA操作
    Thread thread1 = new Thread(() -> {
        int stamp = atomicStampedRef.getStamp();
        // 期望值为100,新值为101
        atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
        // 期望值为101,新值为100
        atomicStampedRef.compareAndSet(101, 100, stamp + 1, stamp + 2);
    });

    // 线程2执行非ABA操作
    Thread thread2 = new Thread(() -> {
        int stamp = atomicStampedRef.getStamp();
        // 期望值为100,新值为102
        atomicStampedRef.compareAndSet(100, 102, stamp, stamp + 1);
    });

    thread1.start();
    thread2.start();

    // 等待两个线程执行完毕
    thread1.join();
    thread2.join();

    // 最终结果
    System.out.println("Result: " + atomicStampedRef.getReference());
}

}