JUC之ABA问题

news2024/11/16 11:34:05

什么是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来保证线程的可见性~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/108013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从注解入手理解springboot原理

一、理解注解 1、注解是什么&#xff1f; 注解可以理解为一个标记或者标签&#xff0c;范围可以是类、方法、属性。 2、自定义一个注解&#xff0c;进行理解spring的自动装配。 自定义自己的注解&#xff1a;JDK官方提供提供了一些基础元注解&#xff0c;就是标记注解的注解…

Android设计模式详解之状态模式

前言 状态模式中的行为是由状态来决定的&#xff0c;不同的状态下有不同的行为。 状态模式和策略模式的结构几乎完全一样&#xff0c;但他们的目的、本质却完全不一样。 状态模式的行为是平行的、不可替换的&#xff0c;而策略模式的行为是彼此独立、可互相替换的。 状态模式把…

满足耐压24V的USB3.0 USB3.1 Type-C防静电器件

什么是USB3.1 Type-C 现有的USB 2.0协议允许高达480Mbps的数据传输速率&#xff0c;并支持即插即用、热插拔安装和运行。与之相比&#xff0c;USB 3.0规范允许高达5Gbps的数据传输速率&#xff0c;并向后支持较低速的USB 2.0规范。 USB 3.0增加了4个接到连接器的新引脚&#…

湖北移动M411A_ZN-S905L3AB-UWE5621DS原机制作语音线刷包

湖北移动M411A_ZN-S905L3AB-UWE5621DS原机制作语音线刷包&#xff0c;打开原机屏蔽的WIFI&#xff0c;设置、首页、语音正常。 固件特点&#xff1a; 1、修改dns&#xff0c;三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&am…

非零基础自学Golang 第16章 正则表达式 16.1 正则表达式介绍 16.2 正则表达式语法

非零基础自学Golang 文章目录非零基础自学Golang第16章 正则表达式16.1 正则表达式介绍16.2 正则表达式语法16.2.1 普通字符16.2.2 字符转义16.2.3 元字符16.2.4 限定符16.2.5 定位符16.2.6 分组构造16.2.7 匹配模式第16章 正则表达式 正则表达式&#xff08;Regular Expressi…

Docker高级:Redis集群实战!4主4从缩容到3主3从,怎么处理?

在上一篇,我们学会了redis集群的扩容。从3主3从扩容到4主4从。 那么,接着,活动过去了。流量没有那么大了。需要缩容了。从4主4从缩容到3主3从了。那么这个时候又该怎么处理呢? PS本系列:《Docker学习系列》教程已经发布的内容,凯哥会在文末加上。 缩容思考问题: 1:…

TypeScript 前端工程最佳实践

作者&#xff1a;王春雨 前言 随着前端工程化的快速发展&#xff0c; TypeScript 变得越来越受欢迎&#xff0c;它已经成为前端开发人员必备技能。 TypeScript 最初是由微软开发并开源的一种编程语言&#xff0c;自2012年10月发布首个公开版本以来&#xff0c;它已得到了人们…

Docker自定义镜像上传阿里云

目录 1. alpine 简介 2. 基于alpine构建jdk8镜像 2.1 Dockerfile 2.2 将构建目录上传到linux中 2.3 执行构建 3. 镜像瘦身 4. 上传阿里云镜像仓库 1. alpine 简介 Alpine Linux是一个轻型Linux发行版&#xff0c;它不同于通常的Linux发行版&#xff0c;Alpine采用了mus…

斯坦福联合Meta提出多模态模型RA-CM3,检索增强机制或成文本图像领域新制胜法宝

原文链接&#xff1a;https://www.techbeat.net/article-info?id4403 作者&#xff1a;seven_ 论文链接&#xff1a; https://arxiv.org/abs/2211.12561 相信目前很多AI大模型研究者都会遇到一个非常困难的问题&#xff0c;那就是很难控制的模型参数和容量&#xff0c;由于参与…

【Three.js入门】纹理及其常用属性、透明纹理、环境遮挡贴图与强度

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

消息队列—RabbitMQ(万字详解)

消息队列—RabbitMQ 笔记整理自 【涛哥】最适合小白入门的RabbitMQ教程 1. 消息队列介绍 Ⅰ. 消息队列需求场景 在基于微服务开发的电商项目中&#xff0c;商品的查询和商品的修改是通过两个服务实现的&#xff0c;如果修改了商品的价格&#xff0c;如何保证商品查询服务查询出…

矢量网络分析仪如何测量史密斯图及滤波器的带宽?

矢量网络分析仪是一种很神奇的测量仪器&#xff0c;它的功能很强大也值得人们去探索。今天&#xff0c;安泰测试工程师就针对矢量网络分析仪中的史密斯图及滤波器的带宽测量进行简单的介绍&#xff0c;希望能够让更多的人对此有所了解&#xff0c;并产生兴趣。 首先仪器了解一下…

数字孪生可视化图表:了解堆叠的条形图

数据可视化已成为我们日常数据分析工作当中的重要组成部分&#xff0c;可视化大屏是数据可视化发展的产物。这又不得不说到一个概念&#xff1a;可视化组件。可视化组件是制作数字孪生可视化大屏必不可少的一部分&#xff0c;在一个完整的数字孪生可视化大屏中用到的图表可不少…

Android系统之路(初识MTK) ------Android11.0给辅助工具-手电筒添加低电Toast提醒

Android11.0给辅助工具-手电筒添加低电Toast提醒 今天因为在复测昨天的一个monkey压测并且还没测完,所以打算记录最近做系统开发的一些心得和经验,也记录一下自己的系统开发历程 修改前的效果图: 修改后的效果图: 后期补上… 修改前当电量小于等于15%时,按钮依旧可以打…

11条非常实用的Git 和 Git 命令,值得你收藏

基本命令 01. init git init 在当前目录中创建一个新的空仓库。 02. clone git clone https://github.com/facebook/react.git 将远程存储库复制到当前目录。 03. add git add 命令将工作目录中的新文件或更改的文件添加到 Git 暂存区。 git add <file1> <file2&…

【WAF绕过-4】漏洞利用之注入上传跨站等绕过

1、SQL注入 如需sqlmap注入测试&#xff0c; 防cc拦截&#xff1a;修改user-agent头代理&#xff08;付费代理池&#xff09; 修改user-agent头&#xff1a; &#xff08;1&#xff09;加参数&#xff1a;--random-agent &#xff08;2&#xff09;加参数&#xff1a;--user-a…

几十年前的老旧照片如何修复?还不知道旧照片怎么恢复清晰吗?

那些旧照片本身会随着时间的推移而褪色、褶皱。老旧照片修复主要是从褪色、变色、破损、发霉、的旧相片修复翻新&#xff0c;到模糊不清清晰化处理&#xff0c;大面积严重破损照片修补。 十年以前&#xff0c;没有电脑&#xff0c;没有手机&#xff0c;更没有存储照片工具。大…

【关于时间序列的ML】项目 5 :用机器学习预测天气

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

基于Canal+kafka监听数据库变化的最佳实践

1、前言 工作中&#xff0c;我们很多时候需要根据某些状态的变化更新另一个业务的逻辑&#xff0c;比如订单的生成&#xff0c;成交等&#xff0c;需要更新或者通知其他的业务。我们通常的操作通过业务埋点、接口的调用或者中间件完成。 但是状态变化的入口比较多的时候&#x…

位图详解.

1.位图概念 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中&#xff1f; 思考&#xff1a; 1.用哈希表&#xff1f;遍历一遍&#xff1f;时间复杂度O(N) 40亿个不重复无符号整数占多大内存&#xff1…