当前位置: 首页 > news >正文

JUC之ABA问题

什么是ABA问题?

ABA问题是由CAS而导致的一个问题

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

比如说一个线程一从内存位置V中取出A,这是另一个线程二也从内存中取出A,并且线程二进行类一些操作将值变成了B,然后线程二又将V位置的数据变成A,这时候线程一进行CAS操作发现内存中仍然是A,然后线程一操作成功!

尽管线程一的CAS操作成功,但是不代表这个过程就是没有问题的。

就比如你家每天,都有一个B流浪汉来你家进行生活,他每天在你回家的时候将家整理成你离开的样子,不留下痕迹。你每天回到家后没有发现什么异常于是就正常生活。虽然他目前没有对你的生活造成什么问题,但是这个过程是很有隐患的!!!

总的来说以前的CAS操作,只管过程,也就是开头和结尾是否相同,如果相同就进行操作!而不管你中间到底进行来什么操作!!

原子引用

如何解决ABA问题呢?首先我们看一下原子引用。我们先前已经使用过java.util.concurrent.atomic下的基本数据类型原子操作类。但是缺少对对象的操作的原子类。也就是AtomicReference

我们来写一个案例来感受一下。

class User{
    String userName;
    int age;

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Offer2019.User{" +
                "userName='" + userName + '\'' +
                ", age=" + age +
                '}';
    }
}

public class AtomicReferenceDemo {

    public static void main(String[] args) {

        User zs = new User("zs", 22);
        User ls = new User("ls", 25);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(zs);

        System.out.println(atomicReference.compareAndSet(zs,ls)+"\t"+atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(zs,ls)+"\t"+atomicReference.get().toString());

    }
}
复制代码

atomicReference.set(zs);讲zs设为当前值

第一个atomicReference.compareAndSet(zs,ls)是将当前值和预期值比较,两者相同都为zs,所以更新为ls,返回true。

第二个atomicReference.compareAndSet(zs,ls)将当前值与预期值比较,当前值为ls预期值为zs比较失败,更新失败,返回false。

带时间戳的原子引用

我们将用到另一个新的类AtomicStampedReference,每次修改值后带有一个版本号!

解决ABA问题

案例

解决ABA问题之前我们先来用代码演示一下什么是ABA问题

public class ABADemo {
    //static方便直接使用
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    //ABA问题
    public static void main(String[] args) {

        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();


        new Thread(() -> {
            try {
                //暂停1秒,保证t1已经完成了一次ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2020)+"\t"+atomicReference.get());
        }, "t2").start();
    }
}
复制代码

这就是ABA问题。

解决

解决ABA问题我们就要使用带时间戳的原子引用AtomicStampedReference。首先查看API

AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);首先给定初始值为100,初始版本号为1。

t3线程一开始通过getStamp()方法拿到目前的初始版本号,后暂停一秒,确保t4线程启动也拿到目前初始的版本号,t4线程启动后暂定3秒,从而保证t3线程的继续执行。t3线程首先通过compareAndSet()方法将预期值,和预期版本与当前值,当前版本分别对比只有两者都相同才将两值更新成更新的值。t3首先将100更新成101,版本号+1变成2。接着将101又更新成了100,版本号+1变成了3,t3线程完成ABA操作。

此时t4线程被调度执行接着继续执行操作,通过compareAndSet()将预期值和当前值比较同为100,但是预期版本号为1,当前版本号已经变成了3两者不一致!!所以更新失败!通过每次操作后版本的变化,从而解决了ABA问题的发生。

public class ABADemo {
    //static方便直接使用
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    //ABA问题
    public static void main(String[] args) {
        //ABA问题的解决
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//版本号
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
            try {
                //确保t4线程拿到初始版本号
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());

        }, "t3").start();

        //ABA问题的解决
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
            try {
                //确保t3线程完成ABA操作
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 2020, stamp, atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp() + "\t当前值为:" + atomicStampedReference.getReference());
        }, "t4").start();
    }
}
复制代码

ABA问题就像一个人总是在你上班的时候闯进你的房子,肆无忌惮的使用房子里的东西,仿佛是房子的主人,却总能在你下班前将房子里的东西还原归位,让你感受不到有人动过的痕迹。虽然看起来相安无事,但是存在很多不可控的因素?身为房子的主人,你也忍受不了这种情况。如何解决呢,就要使用volatile来保证线程的可见性~

相关文章:

  • 8年测试工程师,3年功能,5年自动化,浅谈我的自动化测试进阶之路...
  • 【架构师(第四十九篇)】 服务端开发之认识 Docker-compose
  • TableLayout布局
  • 现在转行学python,前景和优势有哪些?
  • 生成模型(三):基于流的生成模型(Flow-based model)
  • 游戏要从简单做起
  • 作一回白嫖怪:写一个脚本自动获取ST官网积分,用积分领取奖品
  • kali渗透测试系列---信息收集
  • 学python以后做什么工作
  • Python 的列表方法 append 和 extend 有什么区别?
  • 第27章 MySQL 临时表教程
  • Java项目:springboot私人牙医管理系统
  • SpringBoot系列之整合MyBatis框架
  • 低代码多分支协同开发的建设与实践
  • 是谣传还是真强?GitHub一战封神的“SQL优化手册”获赞过百万
  • 2022年终总结-两年Androider的成长之路
  • URLLC应用场景及未来发展研究
  • rabbitmq基础5——集群节点类型、集群节基础运维,集群管理命令
  • 避坑细节拉满!阿里p8技术官私传:MyBatis源码全解析,全彩版附代码分享
  • 想做副业怎么才能找到适合的项目,六条建议让你找副业不再迷茫