zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    静态方法、类方法和属性方法
    类的私有属性和私有方法
    JMeter-正则表达式(取出银行卡号后4位)
    JMeter连接MySQL数据库
    解决chrome提示您的连接不是私密连接的方法
    python安装appium模块
    mac中的word内容丢失
    有些事一旦开始就停不下来了
    Python接口测试-以&连接拼接字典数据(get中url请求数据)
    Python接口测试-模块引用与映射
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/13274209.html
Copyright © 2011-2022 走看看