ABA问题的产生及解决

什么是ABA问题

在CAS算法中,需要取出内存中某时刻的数据(由用户完成),在下一时刻比较并交换(CPU保证原子操作),这个时间差会导致数据的变化。

1、线程1从内存位置V中取出A
2、线程2从内存位置V中取出A
3、线程2进行了写操作,将B写入内存位置V
4、线程2将A再次写入内存位置V
5、线程1进行CAS操作,发现V中仍然是A,交换成功

尽管线程1的CAS操作成功,但线程1并不知道内存位置V的数据发生过改变

ABA问题本质:

根本在于CAS在修改变量的时候,无法记录变量的状态,比如是否修改过这个变量,修改的次数。

public class ABADemo {
    
    private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);

    public static void main(String[] args) {
        new Thread(() -> {
            Integer a = atomicReference.get();
            atomicReference.compareAndSet(a, 101);
            atomicReference.compareAndSet(101, a);
            System.out.println("t1修改了atomicReference的值");
        },"t1").start();
        
        new Thread(() -> {
            Integer b = atomicReference.get();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(b, 2019) + "	修改后的值:" + atomicReference.get());
        },"t2").start();
    }
}

 ABA问题怎么解决

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

    public static void main(String[] args) {
        new Thread(() -> {
            Integer stamp = atomicStampedReference.getStamp();
            System.out.println("t1拿到的初始版本号:"  + stamp);
            //睡眠1秒,是为了让t2线程也拿到同样的初始版本号
            try {
               Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101,stamp,stamp+1);
            stamp = atomicStampedReference.getStamp();
            atomicStampedReference.compareAndSet(101, 100,stamp,stamp+1);
        },"t1").start();
        
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t2拿到的初始版本号:" + stamp);
            //睡眠3秒,是为了让t1线程完成ABA操作
            try {
                Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
            System.out.println("最新版本号:" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "	当前 值:" + atomicStampedReference.getReference());
        },"t2").start();
    }
}

如果不关心引用变量更改了几次,只单纯的关心是否更改过,可以使用AtomicMarkableReference

public class ABADemo {
    
    private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false);

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked());
            //睡眠1秒,是为了让t2线程也拿到同样的初始版本号
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicMarkableReference.compareAndSet(100, 101,false,true);
          
        },"t1").start();
        
        new Thread(() -> {
            boolean isMarked = atomicMarkableReference.isMarked();
            System.out.println("t2版本号是否被更改:" + isMarked);
            //睡眠3秒,是为了让t1线程执行
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("是否更改过:" + atomicMarkableReference.isMarked());
            System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "	当前 值:" + atomicMarkableReference.getReference());
        },"t2").start();
    }
}
原文地址:https://www.cnblogs.com/moris5013/p/11824993.html