解决CAS机制中ABA问题的AtomicStampedReference

CAS(Compare-And-Swap)

CAS即对比交换,它在保证数据原子性的前提下尽可能的减少了锁的使用,很多编程语言或者系统实现上都大量的使用了CAS。

JAVA中CAS的实现

JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令,目前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。

Unsafe提供了三个方法用于CAS操作,分别是

public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);

public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);

public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
  • value 表示 需要操作的对象
  • valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)
  • expect 表示更新时value的期待值
  • update 表示将要更新的值

具体过程为每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false。

ABA问题

线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。
在这里插入图片描述

public class AnswerApp {
    private static AtomicInteger index = new AtomicInteger(100);

    public static void main(String[] args) {
        new Thread(() -> {
            index.compareAndSet(100, 101);
            index.compareAndSet(101, 100);
            System.out.println(String.format("%s: 100->101->100", Thread.currentThread().getName()));
        }, "Thread-1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean success = index.compareAndSet(100, 102);
            System.out.println(String.format("%s: 修改状态=%b, 当前值=%d", Thread.currentThread().getName(), success, index.get()));
        }, "Thread-2").start();
    }
}

程序运行结果

Thread-1: 100->101->100
Thread-2: 修改状态=true, 当前值=102

解决ABA方案

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。
在这里插入图片描述

JAVA中ABA中解决方案(AtomicStampedReference)

AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。

public class ABA {
    private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(String.format("%s 第一次版本号: %d", Thread.currentThread().getName(), stamp));

            stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(String.format("%s 第二次版本号: %d", Thread.currentThread().getName(), stampedReference.getStamp()));

            stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(String.format("%s 第三次版本号: %d", Thread.currentThread().getName(), stampedReference.getStamp()));

        }, "Thread-1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int stamp = stampedReference.getStamp();
            System.out.println(String.format("%s 第一次版本号: %d", Thread.currentThread().getName(), stamp));

            boolean success = stampedReference.compareAndSet(100, 102, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(String.format("%s 修改状态=[%b], 当前值=[%s], 当前版本号=[%s]",
                    Thread.currentThread().getName(), success, stampedReference.getReference(), stampedReference.getStamp()));

        }, "Thread-2").start();
    }

}

程序运行结果

Thread-1 第一次版本号: 1
Thread-1 第二次版本号: 2
Thread-1 第三次版本号: 3
Thread-2 第一次版本号: 3
Thread-2 修改状态=[true], 当前值=[102], 当前版本号=[4]

Reference

发布了152 篇原创文章 · 获赞 27 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/u010979642/article/details/103824766
今日推荐