13、CAS(Compare And Swap)及ABA问题

什么是CAS:
参考1,参考2,

  • CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
  • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

原子类操作实例:(原子类的底层就用到了CAS)

public class TestCAS {

    //CAS ==  compareAndSwap  比较并交换
    public static void main(String[] args) {
        //原子类的底层应用了CAS
        AtomicInteger atomicInteger = new AtomicInteger(20);

        //compareAndSet(int expect, int update)底层就是 unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        //expect:期望值, update更新的值
        //如果期望值到达了,那么就更新,否则就不更新。CAS是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(20, 21));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(20, 21));//更新失败
        System.out.println(atomicInteger.get());
    }
}

结果:

true
21
false
21

Unsafe类:

图片.png

分析原子类的+1方法

atomicInteger.getAndIncrement();


图片.png

图片.png

CAS的ABA问题
*参考1,参考2

  • 在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的数据发生过改变。
例子:

public class TestABA {

    private static AtomicInteger atomicInteger = new AtomicInteger(100);
    public static void main(String[] args) {
        new Thread(() -> {
            atomicInteger.compareAndSet(100, 101);
            atomicInteger.compareAndSet(101, 100);
        },"t1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(100, 2020) + "\t修改后的值:" + atomicInteger.get());
        },"t2").start();
    }
}

*****************************结果:**********************************
true    修改后的值:2020

通过原子引用(AtomicStampedReference):
AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号,
当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功

public class TestABAInAtomicReference {

    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp());

            //睡眠1秒,是为了让t2线程也拿到同样的初始版本号
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
        },"t1").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t2拿到的初始版本号:" + stamp);

            //睡眠3秒主要是为了让t1线程完成ABA操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("最新版本号:" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(100, 2020,stamp,atomicStampedReference.getStamp()+1) + "\t修改后的值:" + atomicStampedReference.getReference());
        },"t2").start();
    }
}
*******************************结果:********************************
t1拿到的初始版本号:1
t2拿到的初始版本号:1
最新版本号:3
false   修改后的值:100

但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。

public class TestABAAtomicMarkableReference {

    private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100,false);

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked());

            //睡眠1秒,是为了让t2线程也拿到同样的初始版本号
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true);
            atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true);
        },"t1").start();

        new Thread(() -> {
            boolean isMarked = atomicMarkableReference.isMarked();
            System.out.println("t2版本号是否被更改:" + isMarked);

            //睡眠3秒,是为了让t1线程完成ABA操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("是否更改过:" + atomicMarkableReference.isMarked());
            System.out.println(atomicMarkableReference.compareAndSet(100, 2020,isMarked,true) + "\t当前 值:" + atomicMarkableReference.getReference());
        },"t2").start();
    }
}
*******************************结果:***********************************
t1版本号是否被更改:false
t2版本号是否被更改:false
是否更改过:true
false   当前 值:100

你可能感兴趣的:(13、CAS(Compare And Swap)及ABA问题)