龙空技术网

CAS导致的ABA问题以及解决方案

上古伪神 1243

前言:

眼前我们对“cas可能出现的问题”大概比较珍视,小伙伴们都需要学习一些“cas可能出现的问题”的相关知识。那么小编在网络上搜集了一些对于“cas可能出现的问题””的相关知识,希望你们能喜欢,咱们快快来了解一下吧!

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

上篇文章讲到CAS会出现一个ABA问题。那什么是ABA问题呢?

官方一点的解释就是:当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。

代码案例:

  //线程操作资源,原子类ai的初始值为4      static AtomicInteger ai = new AtomicInteger(4);      public static void main(String[] args) {        new Thread(() -> {        //利用CAS将ai的值改成5            boolean b = ai.compareAndSet(4, 5);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);            //休眠一秒            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}            //利用CAS将ai的值改回4            b = ai.compareAndSet(5,4);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);        },"A").start();        new Thread(() -> {            //模拟此线程执行较慢的情况            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}            //利用CAS将ai的值从4改为10            boolean b = ai.compareAndSet(4, 10);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);        },"B").start();        //等待其他线程完成,为什么是2,因为一个是main线程,一个是后台的GC线程        while (Thread.activeCount() > 2) {            Thread.yield();        }                System.out.println("ai最终的值为:"+ai.get());    }

执行结果:

可以看到,线程B最终是将ai的值修改成功了。

上面例子模拟的是A、B两个线程操作一个资源ai,A的执行速度比B的快,在B执行前,A就已经将ai的值改为5之后马上又把ai的值改回为4,但是B不感知,所以最后B就修改成功了。

比如有两个单身狗A、B,A在某个时间段内找到女朋友但是又分开了,但是没告诉B,此时B还是会在A是单身狗的情况下带A去打游戏。

ABA问题的解决方案?

数据库有个锁称为乐观锁,是一种基于数据版本实现数据同步的机制,每次修改一次数据,版本就会进行累加。

同样,Java也提供了相应的原子引用类AtomicStampedReference<V>

上图中的初始邮票就是版本号。

根据之前的代码改动的例子:

static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);    public static void main(String[] args) {        new Thread(() -> {            //四个参数分别是预估内存值,更新值,预估版本号,初始版本号       //只有当预估内存值==实际内存值相等并且预估版本号==实际版本号,才会进行修改            boolean b = ai.compareAndSet(4, 5,0,1);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}            b = ai.compareAndSet(5,4,1,2);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);        },"A").start();        new Thread(() -> {            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}            boolean b = ai.compareAndSet(4, 10,0,1);            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);        },"B").start();        while (Thread.activeCount() > 2) {            Thread.yield();        }        System.out.println("ai最终的值为:"+asri.getReference());    }

运行结果:

可以看到,最终B并没有成功修改ai的值

=======================================================

我是Liusy,一个喜欢健身的程序员。

欢迎关注公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。

标签: #cas可能出现的问题