• Java深入学习30:CAS中的ABA问题以及解决方案


    Java深入学习30:CAS中的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 ,线程 2 的修改已经丢失;我们不能忽略线程2对数据的两次修改

    代码模拟ABA问题

      线程1,对数据100进行了两次操作,先将100改成101,再将101改回100;线程2直接将100该成2020;虽然线程2修改成功了,但是在线程2修改之前,线程1已经对100进行了两次操作。线程2修改的100并不是原来的那个100了;

    public class ABATest {
    
        public static void main(String[] args) {
    
            AtomicInteger at = new AtomicInteger(100);
    
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "	" + at.compareAndSet(100,101) + "	 num = " + at.get());
                System.out.println(Thread.currentThread().getName() + "	" + at.compareAndSet(101,100) + "	 num = " + at.get());
            },"thread1").start();
            
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "	" + at.compareAndSet(100,2020) + "	 num = " + at.get());
            },"thread2").start();
        }
    }
    日志
    thread1    true     num = 101
    thread1    true     num = 100
    thread2    true     num = 2020

    如何规避ABA问题

      使用AtomicStampedReference类,简单说AtomicStampedReference类引入了版本概念(类似数据库使用版本号进行乐观锁),每次进行compareAndSet操作是都进行版本好的迭代,只有当同时满足CAS的(1)期望值正确匹配(2)版本号正确匹配,才能正确compareAndSet。

      如下示例,线程2中的 compareAndSet  因为版本匹配错误而返回 flase;

    public class ABASolvedTest {
    
        public static void main(String[] args) {
            AtomicStampedReference<Integer> asr = new AtomicStampedReference(100,1);
    
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "	第1次版本号" + asr.getStamp() + "	 当前值" + asr.getReference());
                asr.compareAndSet(100,101,asr.getStamp(),asr.getStamp()+1);
                System.out.println(Thread.currentThread().getName() + "	第2次版本号" + asr.getStamp() + "	 当前值" + asr.getReference());
                asr.compareAndSet(101,100,asr.getStamp(),asr.getStamp()+1);
                System.out.println(Thread.currentThread().getName() + "	第3次版本号" + asr.getStamp() + "	 当前值" + asr.getReference());
            },"thread1").start();
            new Thread(()->{
                int stamp = asr.getStamp();
                System.out.println(Thread.currentThread().getName() + "	第1次版本号" +stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean b = asr.compareAndSet(100, 2019, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName() + "	是否更新成功 " + b);
                System.out.println(Thread.currentThread().getName() + "	更新后的版本号" + asr.getStamp());
                System.out.println(Thread.currentThread().getName() + "	更新后的值" + asr.getReference());
            },"thread2").start();
        }
    }
    日志
    thread2    第1次版本号1
    thread1    第1次版本号1     当前值100
    thread1    第2次版本号2     当前值101
    thread1    第3次版本号3     当前值100
    thread2    是否更新成功 false
    thread2    更新后的版本号3
    thread2    更新后的值100

    END

  • 相关阅读:
    序列化
    输入输出流——字符流部分
    输入输出流——字节流部分
    GitHub10岁之际HanLP自然语言处理包用户量跃居榜首
    hadoop大数据处理平台与案例
    dkhadoop的自然语言处理技术介绍
    hadoop大数据技术架构详解
    hadoop框架结构介绍
    Hadoop集群环境搭建步骤说明
    快速了解掌握中文自然语言处理
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/13274209.html
Copyright © 2020-2023  润新知