16.ABA问题

news2024/11/13 10:09:03

文章目录

  • ABA问题
    • 1.什么是ABA问题?
    • 2.ABA问题解决方案
      • 2.1.使用AtomicStampedReference解决ABA问题
      • 2.2.使用AtomicMarkableReference解决ABA问题

ABA问题

因为CAS操作的原子性能高,在JUC中广泛被应用,但是如果使用的不合理,CAS操作就会存在ABA问题!

1.什么是ABA问题?

ABA问题是指在并发编程中,由于CAS(比较并交换)操作的特性,可能出现的一种问题。CAS是一种乐观锁机制,用于实现多线程环境下的原子操作。它通常包含三个参数:期望值更新值当前值。CAS操作会比较当前值和期望值,如果相等,则将当前值更新为新值,否则不进行任何操作。

CAS操作的局限性导致了ABA问题的出现。具体而言,ABA问题通常在以下情况下出现:

  1. 并发场景下的共享变量修改:多个线程同时对一个共享变量进行修改和操作。
  2. CAS操作的原子性:CAS操作只能检查共享变量当前的值是否和预期值相等,并在相等时才进行更新操作。
  3. 中间值的变化:如果在CAS操作执行过程中,共享变量的值经历了从预期值A到其他值B,再回到A的变化,那么CAS操作可能会错误地认为共享变量的值没有发生变化。

举例来说,假设一个共享变量的初始值为A,在某个线程执行CAS操作之前,另一个线程将该变量的值从A修改为B,然后再将其修改回A。如果执行CAS操作的线程只关注共享变量的当前值是否和预期值相等,而不考虑中间值的变化,那么CAS操作会错误地认为共享变量的值没有发生变化,导致出现问题。

因此,ABA问题的产生是由于CAS操作只关注共享变量的当前值,而忽略了中间值的变化。这种问题可能会导致并发控制失效,数据不一致等后果,因此需要针对性地设计解决方案来解决ABA问题。

下面通过一个案例,来了解一下ABA问题

假设现在有一个栈,该栈是采用单链表进行实现的,元素的插入和删除都发生在单链表的头部。

假设,线程1 和线程2 是两在堆栈上并发操作的线程,其中线程1计划从head位置通过CAS操作进行元素“B”的弹出操作

在这里插入图片描述

此时线程1刚好启动CAS执行,但是还没有开始执行,线程2抢在线程1前面弹出了元素“B”,并且压入了一个新的元素“C”,再压入了“B”,此时通过CAS操作后 head位置还是“B”

这个时候切换到线程1执行,通过CAS发现线程2的位置还是 “B”操作成功!将“B”弹出并压入新的元素“B”,尽管线程1操作成功,但是这样会存在一个很大问题

在这里插入图片描述

1.已知 栈顶的元素为 B,这个时候线程A知道 B.next = NULL , 希望使用CAS操作,CAS(B,A)将栈顶的元素替换成A,从而将B元素从堆栈中弹出

在这里插入图片描述

2.但是由于线程1 和线程2是并发执行的,CPU时间片不巧分配给了线程2,线程2弹出了B,A元素,然后压入了C,B,最终线程又将head的位置变成了B

在这里插入图片描述

3.接下来线程1从新获得CPU时间片,执行CAS(B,A),操作成功,将B弹出压入了但是此时,链表就断开了,C元素不在存储于堆栈中了,被丢掉了!

在这里插入图片描述

这就是ABA引发的不正常的状态

2.ABA问题解决方案

很多乐观锁的实现版本都是使用版本号(version)的方式来解决ABA问题。每次对共享变量进行修改时,版本号都会相应地递增。这样,在执行CAS操作时,除了比较共享变量的值外,还会比较版本号,从而更精确地判断共享变量是否发生了变化。

2.1.使用AtomicStampedReference解决ABA问题

AtomicStampedReference 是 Java 并发包中提供的一个工具类,用于解决 CAS 操作中的 ABA 问题。与 AtomicReference 不同,AtomicStampedReference 在每个引用值的基础上都附加了一个整型的标记(stamp),用来记录引用值的版本号。这个版本号在每次更新引用值时会自动增加,因此可以用来检测引用值是否发生过变化,从而避免 ABA 问题。

具体来说,AtomicStampedReference 的主要特性包括:

  1. 原子性操作: AtomicStampedReference 提供了一系列原子性的操作方法,包括 compareAndSetgetset 等,确保多线程环境下的安全访问。
  2. 版本号标记: 每个引用值都与一个整型的版本号相关联,用于标记引用值的版本信息。版本号会在每次更新引用值时自动增加,从而在CAS操作中额外检测引用值的变化。
  3. 解决ABA问题: 通过版本号标记,AtomicStampedReference 可以检测到引用值的变化过程,即使引用值发生了从 A 到 B 再到 A 的变化,只要版本号不同,CAS操作就会失败,从而避免了ABA问题。

使用 AtomicStampedReference 解决ABA问题的一般步骤如下:

  1. 初始化 AtomicStampedReference,并指定初始的引用值和版本号。
  2. 在每次对共享变量进行修改时,使用 AtomicStampedReference 的原子性方法,同时更新引用值和版本号。
  3. 在CAS操作中,除了比较共享变量的值外,还要比较版本号是否与预期版本号相同。只有在共享变量的值和版本号都与预期值相同时,才执行更新操作。

通过这种方式,AtomicStampedReference 可以有效地解决CAS操作中的ABA问题,提高了并发环境下的安全性和可靠性。

    @Test
    @DisplayName("使用AtomicStampedReference解决ABA问题")
    public void testAtomicStampedReference() throws InterruptedException {
        // 初始化 AtomicStampedReference,初始值为初始对象和版本号为0
        AtomicStampedReference<String> atomicStampedRef = new AtomicStampedReference<>("初始值", 0);

        // 创建两个线程,分别进行CAS操作
        Thread thread1 = new Thread(() -> {
            // 获取当前版本号
            int stamp = atomicStampedRef.getStamp();
            // 获取当前引用值
            String value = atomicStampedRef.getReference();
            log.info("线程1开始执行,当前版本号:{},当前值:{}", stamp, value);
            // 模拟线程1执行耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 尝试更新引用值和版本号
            boolean success = atomicStampedRef.compareAndSet(value, "新值1", stamp, stamp + 1);
            if (success) {
                log.info("线程1更新成功,新版本号:{}", stamp + 1);
            } else {
                log.error("线程1更新失败,当前值已经被其他线程修改");
            }
        });

        Thread thread2 = new Thread(() -> {
            // 获取当前版本号
            int stamp = atomicStampedRef.getStamp();
            // 获取当前引用值
            String value = atomicStampedRef.getReference();
            log.info("线程2开始执行,当前版本号:{},当前值:{}", stamp, value);
            // 尝试更新引用值和版本号
            boolean success = atomicStampedRef.compareAndSet(value, "新值2", stamp, stamp + 1);
            if (success) {
                log.info("线程2更新成功,新版本号:{}", stamp + 1);
            } else {
                log.error("线程2更新失败,当前值已经被其他线程修改");
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完成
        thread1.join();
        thread2.join();

        // 输出最终结果
        log.info("最终值:{}", atomicStampedRef.getReference());
    }

在这里插入图片描述

2.2.使用AtomicMarkableReference解决ABA问题

AtomicMarkableReference 是 AtomicStampedReference 简化版本,他不关心修改过几次,他只关心有没有被修改过。

  1. AtomicMarkableReference 是 Java 中提供的一种原子引用类,用于解决 CAS 操作中的 ABA 问题。与 AtomicStampedReference 类似,AtomicMarkableReference 也使用了版本号(mark)的方式来确保 CAS 操作的正确性。

    特性:

    1. AtomicMarkableReference 内部包含了一个引用对象和一个标记(mark),它们共同构成了一个原子更新单元。
    2. 标记(mark)是一个布尔值,用于辅助 CAS 操作,标记位的变化表明了引用对象的状态变化。
    3. 提供了 compareAndSet 方法来进行原子更新,确保引用对象和标记的更新是原子性的。
    4. 使用 AtomicMarkableReference 可以避免 CAS 操作中的 ABA 问题,从而更安全地进行并发编程。

    使用过程:

    1. 创建 AtomicMarkableReference 对象时,需要传入初始引用对象和初始标记。
    2. 使用 get 方法可以获取当前的引用对象和标记。
    3. 使用 compareAndSet 方法进行原子更新,需要传入旧的引用对象、新的引用对象、旧的标记和新的标记,只有在旧的引用对象和标记与当前值相同时,才会更新为新的值。
    4. 提供了一种线程安全的机制,可以避免 CAS 操作中的 ABA 问题,确保并发操作的安全性。
    @Test
    @DisplayName("测试使用AtomicMarkableReference解决ABA问题")
    public void testAtomicMarkableReference() throws InterruptedException {
        // 初始化 AtomicMarkableReference,初始值为初始对象和标记为false
        AtomicMarkableReference<String> atomicMarkableRef = new AtomicMarkableReference<>("初始值", false);

        // 创建两个线程,分别进行CAS操作
        Thread thread1 = new Thread(() -> {
            // 获取当前标记
            boolean[] markHolder = new boolean[1];
            // 获取当前引用值
            String value = atomicMarkableRef.get(markHolder);
            log.info("线程1开始执行,当前标记:{},当前值:{}", markHolder[0], value);
            // 模拟线程1执行耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 尝试更新引用值和标记
            boolean success = atomicMarkableRef.compareAndSet(value, "新值1", markHolder[0], true);
            if (success) {
                log.info("线程1更新成功,新标记:{}", true);
            } else {
                log.error("线程1更新失败,当前值已经被其他线程修改");
            }
        });

        Thread thread2 = new Thread(() -> {
            // 获取当前标记
            boolean[] markHolder = new boolean[1];
            // 获取当前引用值
            String value = atomicMarkableRef.get(markHolder);
            log.info("线程2开始执行,当前标记:{},当前值:{}", markHolder[0], value);
            // 尝试更新引用值和标记
            boolean success = atomicMarkableRef.compareAndSet(value, "新值2", markHolder[0], true);
            if (success) {
                log.info("线程2更新成功,新标记:{}", true);
            } else {
                log.error("线程2更新失败,当前值已经被其他线程修改");
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完成
        thread1.join();
        thread2.join();

        // 输出最终结果
        log.info("最终值:{}", atomicMarkableRef.getReference());
    }

在这里插入图片描述

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

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

相关文章

创新方便的在线评审软件——原理图设计的利器

概述 在当今数字化时代&#xff0c;原理图设计已成为许多领域中不可或缺的一环。然而&#xff0c;对于设计师们来说&#xff0c;如何高效地进行评审却是一个挑战。 传统的评审方式往往需要多人齐聚一堂&#xff0c;耗时耗力。设计师们需要亲自填写评审意见&#xff0c;这不仅…

表现层设计模式_1.MVC模式

1.MVC模式三个核心模块 MVC是一种目前广泛流行的软件设计模式。近年来&#xff0c;随着Java EE的成熟&#xff0c;MVC成为了Java EE平台上推荐的一种设计模式。MVC强制性地把一个应用的输入、处理、输出流程按照视图、控制、模型的方式进行分离&#xff0c;形成了控制器…

算法学习(7)-树

目录 开启“树”之旅 二叉树 堆--优先队列 并查集 开启“树”之旅 是不是很像一棵倒挂的树&#xff1f;也就是说它是根朝上&#xff0c; 而叶子朝下的。不像&#xff1f;哈哈&#xff0c;来看看下面的图你就会觉得像啦。 你可能会间&#xff1a; 树和图有什么区别&#xff…

Hive的排序

order by 全局排序,因此只有一个reducer,设置了多个reducer也不起作用.-- 设置reducer的个数为2 set mapred.reduce.tasks2; -- 查询 select * from user_install_status_limit order by uptime limit 10;order by 在hive.mapred.mode strict 模式下,必须指定 limit,否则执行…

东莞酷得电子方案 遥控水弹坦克车

首先遥控小车是一种能够通过无线遥控器进行远程操控的小型机器人。遥控小车应用了哪些软硬件技术呢&#xff1f;本文将从以下几个方面进行详细介绍。 遥控小车应用了多种软硬件技术&#xff0c;涉及底盘结构、动力系统、传感器、控制器等多个方面。 底盘结构&#xff1a;遥控…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷5(私有云)

#需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包…

prompt工程策略(三:使用 LLM 防护围栏创建系统提示)

原文&#xff1a;我是如何赢得GPT-4提示工程大赛冠军的 原文的原文&#xff1a; How I Won Singapore’s GPT-4 Prompt Engineering Competition &#xff01;&#xff01;本内容仅适用于具有 System Prompt&#xff08;系统提示&#xff09;功能的 LLM。具有这一功能的最著名 …

vue-pure-admin项目内复制文字粘贴到word中之后存在边框问题

vue-pure-admin项目内复制文字粘贴到word中之后存在黑色边框是由于reset.scss文件内设置了通配符的border样式 修改前 代码 *, ::before, ::after {box-sizing: border-box;// 添加这个样式会导致复制的文字粘贴到word中带有边框问题border-color: currentColor;border-styl…

优秀博士学位论文分享:复杂场景下高精度有向目标检测的研究

优秀博士学位论文代表了各学科领域博士研究生研究成果的最高水平&#xff0c;本公众号近期将推出“优秀博士学位论文分享”系列文章&#xff0c;对人工智能领域2023年优秀博士学位论文进行介绍和分享&#xff0c;方便广大读者了解人工智能领域最前沿的研究进展。 “博士学位论…

华为Pura独立?或将成立全新子品牌

近日&#xff0c;华为官方公布了P系列将正式升级为“Pura”系列的消息&#xff0c;并且有可能演变成为一个全新的子品牌。多年以来&#xff0c;P系列一直以影像功能与颇具时尚感的设计而闻名。而这次品牌升级似乎并不局限于智能手机&#xff0c;经营范围似乎覆盖了手表、珠宝等…

CleanMyMac X v4.14.6中文破解版,让您的电脑像新的一样

小编给您带来CleanMyMac X v4.14.6中文破解版&#xff0c;CleanMyMac X破解版是应用在MacOS上的一款Mac系统清理优化工具&#xff0c;使用cleanmymac x 中文破解版只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉&#xff0c;节省宝贵的磁盘空间。 CleanMyMa…

STM32的FLASH学习笔记

不同型号的 STM32&#xff0c;其 FLASH 容量也有所不同&#xff0c;最小的只有 16K 字节&#xff0c;最大的则达到了1024K 字节。大容量产品的闪存模块组织如图所示&#xff1a; STM32 的闪存模块由&#xff1a;主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。 ​ ①主…

【LInux】<基础IO> 文件操作 | 文件描述符 | 重定向

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

OpenHarmony 实战开发——ArkUI容器类API介绍

容器类&#xff0c;顾名思义就是存储的类&#xff0c;用于存储各种数据类型的元素&#xff0c;并具备一系列处理数据元素的方法。在 ArkUI 开发框架中&#xff0c;容器类采用了类似静态的语言来实现&#xff0c;并通过 NAPI 框架对外提供。通过对存储位置以及属性的限制&#x…

如何找到MySQL中存储引擎所对应的表空间并且打开?

在上节课我们学习了数据库&#xff08;MySQL&#xff09;进阶&#xff1a;存储引擎&#xff0c;有不少同学产生疑惑&#xff0c;到底要怎么找到表空间并且打开啊&#xff1f;这节课我们就来探讨。 首先&#xff0c;根据这个路径&#xff1a;C:\ProgramData\MySQL\MySQL Server…

k8s的网路配置

目录 1、k8s相关网络类型 1.1 K8S中Pod网络通信 1.2 Overlay Network 1.3 VXLAN 1.3.1 vlan和vxlan的区别 2、Flannel 2.1 简介 2.2 Flannel工作原理 2.3 ETCD之Flannel提供说明 2.4 Flannel部署 2.4.1 在node节点上操作 2.4.2 在master01节点上操作 2.4.2.1 安装f…

SVDD(Singing Voice Deepfake Detection,歌声深度伪造检测)挑战2024

随着AI生成的歌声快速进步&#xff0c;现在能够逼真地模仿自然人类的歌声并与乐谱无缝对接&#xff0c;这引起了艺术家和音乐产业的高度关注。歌声与说话声不同&#xff0c;由于其音乐性质和强烈的背景音乐存在&#xff0c;检测伪造的歌声成为了一个特殊的领域。 SVDD挑战是首个…

Android实践:查看Activity信息

问题&#xff1a;本地Android SDK的monitor无法正常运行&#xff0c;看不了进程相关信息&#xff0c;确认当前显示Activity十分不便 解决办法&#xff1a;使用adb shell指令可以快速查看 命令&#xff1a; adb shell dumpsys activity activities 这个命令用于获取Android设…

精酿啤酒:品质的保障与消费者的信赖

在啤酒市场中&#xff0c;Fendi club啤酒以其卓着的品质和消费者的信赖赢得了广泛的认可。作为精酿啤酒的品牌&#xff0c;Fendi club啤酒始终坚持对品质的严格把控&#xff0c;为消费者带来放心的口感体验。 品质的保障源于Fendi club啤酒对原料的严谨挑选和加工。他们深知&a…

LeetCode刷题笔记第1800题:最大升序子数组和

LeetCode刷题笔记第1800题&#xff1a;最大升序子数组和 题目&#xff1a; 想法&#xff1a; 遍历数组的同时记录当前最大升序子数组和&#xff0c;最终返回最大升序子数组和 class Solution:def maxAscendingSum(self, nums: List[int]) -> int:result 0i 0n len(num…