zoukankan      html  css  js  c++  java
  • ABA 问题

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

    比如说一个线程 one 从内存位置 V 中取出A,这时候另外一个线程 two 也从内存中取出 A,并且线程 two进行了一些操作将值变成了B,然后线程 two 又将 V 位置的数据变成 A,这时候线程 one 进行CAS操作发现内存中仍然是 A,然后线程 one 操作成功 。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的

    原子引用

    原子引用其实和原子包装类是差不多的概念,就是将一个 java 类,用原子引用类进行包装起来,那么这个类就具备了原子性 。

    @Data
    @ToString
    class User {
        String userName;
        int age;
    }
    public class AtomicReferenceDemo {
    
        public static void main(String[] args) {
    
            User z3 = new User("z3", 22);
    
            User l4 = new User("l4", 25);
    
            // 创建原子引用包装类
            AtomicReference<User> atomicReference = new AtomicReference<>();
    
            // 现在主物理内存的共享变量为z3
            atomicReference.set(z3);
    
            // 比较并交换,如果现在主物理内存的值为z3,那么交换成l4
            System.out.println(atomicReference.compareAndSet(z3, l4) + "	 " + atomicReference.get().toString());
    
            // 比较并交换,现在主物理内存的值是l4了,但是预期为z3,因此交换失败
            System.out.println(atomicReference.compareAndSet(z3, l4) + "	 " + atomicReference.get().toString());
        }
    }
    

    基于原子引用的 ABA 问题

    我们首先创建了两个线程,然后 T1 线程,执行一次 ABA 的操作,T2 线程在一秒后修改主内存的值

    public class ABADemo {
    
        /**
         * 普通的原子引用包装类
         */
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    
        public static void main(String[] args) {
    
            new Thread(() -> {
                // 把100 改成 101 然后在改成100,也就是ABA
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    // 睡眠一秒,保证t1线程,完成了ABA操作
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 把100 改成 101 然后在改成100,也就是ABA
                System.out.println(atomicReference.compareAndSet(100, 2019) + "	" + atomicReference.get());
    
            }, "t2").start();
        }
    }
    

    运行后成功的修改了,这就是 ABA 问题

    解决 ABA 问题
    在java的并发包下,有AtomicStampedReference这个类,可以解决ABA问题。其原理其实就是新增一种机制,修改增加版本号,类似于时间戳 的概念

    T1: 100 1 2019 2

    T2: 100 1 101 2 100 3

    如果 T1 修改的时候,版本号为 2,落后于现在的版本号 3,所以要重新获取最新值,这里就提出了一个使用时间戳版本号,来解决 ABA 问题的思路 。

    AtomicStampedReference
    时间戳原子引用,来这里应用于版本号的更新,也就是每次更新的时候,需要比较期望值和当前值,以及期望版本号和当前版本号 。

    public class ABADemo {
    
        /**
         * 普通的原子引用包装类
         */
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    
        // 传递两个值,一个是初始值,一个是初始版本号
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
    
        public static void main(String[] args) {
    
            System.out.println("============以下是ABA问题的产生==========");
    
            new Thread(() -> {
                // 把100 改成 101 然后在改成100,也就是ABA
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    // 睡眠一秒,保证t1线程,完成了ABA操作
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 把100 改成 101 然后在改成100,也就是ABA
                System.out.println(atomicReference.compareAndSet(100, 2019) + "	" + atomicReference.get());
    
            }, "t2").start();
    
            System.out.println("============以下是ABA问题的解决==========");
    
            new Thread(() -> {
    
                // 获取版本号
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	 第一次版本号" + stamp);
    
                // 暂停t3一秒钟
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 传入4个值,期望值,更新值,期望版本号,更新版本号
                atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
    
                System.out.println(Thread.currentThread().getName() + "	 第二次版本号" + atomicStampedReference.getStamp());
    
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
    
                System.out.println(Thread.currentThread().getName() + "	 第三次版本号" + atomicStampedReference.getStamp());
    
            }, "t3").start();
    
            new Thread(() -> {
    
                // 获取版本号
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	 第一次版本号" + stamp);
    
                // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp+1);
    
                System.out.println(Thread.currentThread().getName() + "	 修改成功否:" + result + "	 当前最新实际版本号:" + atomicStampedReference.getStamp());
    
                System.out.println(Thread.currentThread().getName() + "	 当前实际最新值" + atomicStampedReference.getReference());
    
    
            }, "t4").start();
    
        }
    }
    

    我们能够发现,线程 t3,在进行 ABA 操作后,版本号变更成了 3,而线程 t4 在进行操作的时候,就出现操作失败了,因为版本号和当初拿到的不一样 。

  • 相关阅读:
    2020年4月13日
    2021年4月12日
    梦断代码阅读笔记02
    Shell基本命令
    远程链接Linux
    Linux文档与目录结构
    VMware与Centos系统安装
    linux 第一天
    day88 Vue基础
    python 生成随机验证码
  • 原文地址:https://www.cnblogs.com/weianlai/p/14589513.html
Copyright © 2011-2022 走看看