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

  • 相关阅读:
    理解MySQL——索引与优化
    Android中shape的使用
    Android之Camera控制拍照
    android的fragments管理
    android的fragment基本介绍
    android的animator
    android软键盘弹出隐藏的监听
    android平板Home键的监听
    android jsonarray
    android 应用静默自启动的解决方法
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/13274209.html
Copyright © 2011-2022 走看看