【JavaEE】多线程CAS中的aba问题是什么?

news2025/1/24 5:39:35

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE初阶

什么是CAS问题?CAS: 全称Compare and swap,字面意思:”比较并交换“,CAS中的aba问题是什么?请看本文讲解~~


目录

文章目录

一、CAS是什么?

二、CAS是怎么实现的?

三、CAS有哪些应用

3.1 实现原子类

3.2实现自旋锁

四、CAS的aba问题

          4.1什么是aba问题?

          4.2 aba问题带来的bug

五、相关面试题

5.1 讲解下你自己理解的 CAS 机制

5.2 ABA问题怎么解决?


一、CAS是什么?

CAS: 全称Compare and swap,字面意思:”比较并交换“,寄存器A的值和内存M的值进行对比,如果值相同,就把寄存器B的值和M的值进行交换~~一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
1. 比较 A 与 V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V。(交换)
3. 返回操作是否成功。

CAS伪代码

下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的. 这个伪代码只是辅助理解CAS 的工作流程

boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}

  

关键的是,CAS操作,是一条CPU指令(原子的)!!并非是上述这一段代码,这一条指令就能完成上述这一段代码的功能~~

两种典型的不是 "原子性" 的代码
1. check and set (if 判定然后设定值) [上面的 CAS 伪代码就是这种形式]
2. read and update (i++) [之前我们讲线程安全的代码例子是这种形式]

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号
CAS 可以视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式)

二、CAS是怎么实现的?

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。
简而言之,是因为硬件予以了支持,软件层面才能做到。

三、CAS有哪些应用

3.1 实现原子类

标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的。
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作

AtomicInteger 类能够保证++ -- 的时候线程安全,并非是加锁

AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();

伪代码实现:

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
               oldValue = value;
        }
        return oldValue;
    }
}

假设两个线程同时调用 getAndIncrement
1) 两个线程都读取 value 的值到 oldValue 中. (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)

 2) 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值

注意:
CAS 是直接读写内存的, 而不是操作寄存器.
CAS 的读内存, 比较, 写内存操作是一条硬件指令, 是原子的.

 3) 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环.
在循环里重新读取 value 的值赋给 oldValue

 4) 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作.

 5) 线程1 和 线程2 返回各自的 oldValue 的值即可.

通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.
本来 check and set 这样的操作在代码角度不是原子的. 但是在硬件层面上可以让一条指令完成这
个操作, 也就变成原子的了.

CAS本身对应一条CPU指令(不可拆分的最小单位了)

3.2实现自旋锁

反复检查当前的锁状态,看是否解开了~~

基于 CAS 实现更灵活的锁, 获取到更多的控制权.
自旋锁伪代码:

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有.
        // 如果这个锁已经被别的线程持有, 那么就自旋等待.
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
        while(!CAS(this.owner, null, Thread.currentThread())){
        }
}
    public void unlock (){
        this.owner = null;
    }
}

 如果当前owner是null,比较就成功,就把当前线程的引用设置到owner中,加锁完成!!循环结束

比较不成功~,意味着owner非空,锁已经有线程持有了~~此时CAS就啥也不干,直接返回false,循环继续进行~~

此时这个循环就会转的飞快,不停的尝试询问这里的锁是不是释放!!

好处,一旦锁释放,就立即能获取到!!

坏处,cpu忙等~~

一般乐观锁,这个情况下(锁冲突概率低)实现成自旋锁比较合适的~~

四、CAS的aba问题

4.1什么是aba问题?

ABA 的问题:
假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要

  • 先读取 num 的值, 记录到 oldNum 变量中.
  • 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.

但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A
到这一步, t1 线程无法区分当前这个变量始终是 A, 还是经历了一个变化过程

这就好比, 我们买一个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手
机。

那么如何去解决这个问题呢??aba的关键是值会反复横跳~~如果约定数据只能单方向变化,问题就迎刃而解了(只能增加,或者只能减小)

如果需求要求该数值,既能增加也能减小,应该怎么办?可以引入另外一个版本号变量,约定版本号只能增加~~

每次CAS对比的时候,就不是对比数值本身,而是对比版本号!!

4.2 aba问题带来的bug

大部分的情况下, t2 线程这样的一个反复横跳改动, 对于 t1 是否修改 num 是没有影响的. 但是不排除一些特殊情况

假设 滑稽老哥 有 100 存款. 滑稽想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执行 -50操作.
我们期望一个线程执行 -50 成功, 另一个线程 -50 失败.
如果使用 CAS 的方式来完成这个扣款过程就可能出现问题.
正常的过程
1) 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.
2) 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.
3) 轮到线程2 执行了, 发现当前存款为 50, 和之前读到的 100 不相同, 执行失败.

异常的过程
1) 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.
2) 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.
3) 在线程2 执行之前, 滑稽的朋友正好给滑稽转账 50, 账户余额变成 100 !!
4) 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行扣款操作
这个时候, 扣款操作被执行了两次!!! 都是 ABA 问题搞的鬼!!

解决方案:

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.
CAS 操作在读取旧值的同时, 也要读取版本号.
真正修改的时候,
如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)

这就好比, 判定这个手机是否是翻新机, 那么就需要收集每个手机的数据, 第一次挂在电商网站上的手机记为版本1, 以后每次这个手机出现在电商网站上, 就把版本号进行递增. 这样如果买家不在意这是翻新机, 就买. 如果买家在意, 就可以直接略过.

对比立即上面的转账例子:

假设 滑稽老哥 有 100 存款. 滑稽想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执行 -50操作.
我们期望一个线程执行 -50 成功, 另一个线程 -50 失败.
为了解决 ABA 问题, 给余额搭配一个版本号, 初始设为 1.
1) 存款 100. 线程1 获取到 存款值为 100, 版本号为 1, 期望更新为 50; 线程2 获取到存款值为 100,版本号为 1, 期望更新为 50.
2) 线程1 执行扣款成功, 存款被改成 50, 版本号改为2. 线程2 阻塞等待中.
3) 在线程2 执行之前, 滑稽的朋友正好给滑稽转账 50, 账户余额变成 100, 版本号变成3.
4) 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 但是当前版本号为 3, 之前读到的版本号为 1, 版本小于当前版本, 认为操作失败

五、相关面试题

5.1 讲解下你自己理解的 CAS 机制

全称 Compare and swap, 即 "比较并交换". 相当于通过一个原子的操作, 同时完成 "读取内存, 比
较是否相等, 修改内存" 这三个步骤. 本质上需要 CPU 指令的支撑

5.2 ABA问题怎么解决?

给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.
如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当
前版本号比之前读到的版本号大, 就认为操作失败

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

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

相关文章

2023二建学天案例突破101问

2023 年二级建造师《公路》案例 101 问1.哪些情况下应进行长度宜不小于 200m的试验路段施工。(1)二级及二级以上公路路堤。(2)填石路堤、土石路堤;(3)特殊填料路堤;(4)特殊路基;(5)拟采用新技术、新工艺、新材料,新设备的路系。2.石质路暂的开挖方式有哪些!(1)钻爆开…

【笔记】响应表头中的Content-disposition

问题来源: 今天在做关于 怎样不通过使用插件的方式在HTML上预览本地C盘下的PDF文件,在生成PDF文件到C盘后,我想在下载和生成之间,再加一个PDF预览,就是先生成到C盘,再由用户来预览之后再决定是否下载&…

ModelNet40数据集

跑PointNet,modelnet40数据集时; 有些人直接用.off文件;——【CAD模型】普林斯顿形状Banchmark中的.off文件遵循以下标准: OFF文件全是以OFF关键字开始的ASCII文件。下一行说明顶点的数量、面片的数量、边的数量。 边的数量可以安全地省略。对模型不会有影响(可以为…

4/16学习周报

文章目录前言文献阅读摘要简介方法评估标准结果结论时间序列预测支持向量机SVM高斯过程GPRNNLSTMGRUtransformer总结前言 本周阅读文献《Real-time probabilistic forecasting of river water quality under data missing situation: Deep learning plus post-processing tech…

环境变量概念详解!(4千字长文)

环境变量! 文章目录环境变量!环境变量PATHexportexport的错误用法定义命令行变量环境变量哪里来的其他各种环境变量HOMEHOSTNAMELOGNAMEHISTSIZEPWD环境变量相关指令echoenvgetenv——相关函数!exportsetunset命令行参数argcargvenvpenvironp…

stata绘图指令

stata绘图指令 – 潘登同学的stata笔记 文章目录stata绘图指令 -- 潘登同学的stata笔记绘图概览韦恩图折线图连线图线性拟合图直方图函数图添加特殊字符和文字绘图概览 Stata 提供的图形种类: twoway 二维图scatter 散点图line 折线图area 区域图lfit 线性拟合图q…

漏洞扫描工具AWVS的安装及配置使用过程

简介 Acunetix Web Vulnerability Scanner(AWVS)可以扫描任何通过Web浏览器访问和遵循HTTP/HTTPS规则的Web站点。适用于任何中小型和大型企业的内联网、外延网和面向客户、雇员、厂商和其它人员的Web网站。 AWVS可以通过检查SQL注入攻击漏洞、XSS跨站脚…

LinuxGUI自动化测试框架搭建(三)-虚拟机安装(Hyper-V或者VMWare)

(三)-虚拟机安装(Hyper-V或者VMWare)1 Hyper-V安装1.1 方法一:直接启用1.2 方法二:下载安装1.3 打开Hyper-V2 VMWare安装注意:Hyper-V或者VMWare只安装一个,只安装一个,只…

SQL——34道经典例题之1-17

目录 1 查询每个部门最高薪水的人员名称 2 查询哪些人的薪水在部门平均薪水之上 3 查询每个部门的平均薪水等级 3.1 每个部门的平均薪水的等级 3.2 每个部门的平均的薪水等级 4 查询最高薪水(不用max函数) 5 查询平均薪水最高的部门的部门编号 …

FE_CSS CSS 的三大特性

1 层叠性 相同选择器给设置相同的样式,此时一个样式就会覆盖(层叠)另一个冲突的样式。层叠性主要解决样式冲突的问题 层叠性原则: 样式冲突,遵循的原则是就近原则,哪个样式离结构近,就执行哪个…

「C/C++」C/C++预处理器

博客主页:何曾参静谧的博客 文章专栏:「C/C」C/C学习 目录一、宏替换 #define1. 定义常量2. 定义函数3. 定义代码块二、条件编译 #if1. 使用 #ifdef 和 #endif 编译不同平台的代码2. 使用 #if 和 #else 编译不同版本的代码3. 使用 #ifndef 和 #define和#…

机器学习 00 交叉验证

一、什么是交叉验证(cross validation) 交叉验证:将拿到的训练数掘,分为训练和验证集。以下图为例: 将数据分成4份,其中一份作为验证集。然后经过4次(组)的测试,每次都更换不同的验证集。即得到4组模型的结果,取平均值作为最终结…

ENVI 5.6软件安装教程

软件下载 [软件名称]:ENVI 5.6 [软件大小]:3.25G [安装环境]:Win7~Win11或更高 软件介绍 ENVI 5.6是一款实现遥感图像处理的工具,已经广泛应用于科研、环境保护、气象、石油矿产勘探、农业、林业、医学、地球科学、公用设施管…

RK3568平台使用PyQt5遇到的_ZTI18QOpenGLTimeMonitor, version Qt_5问题解决

1、背景 由于开发需要在ubuntu 20.04 RK3568平台上面使用PyQt5来运行GUI软件,整个软件的环境如下:python3.8 PyQt5 5.14.1版本 fireflyfirefly:/usr/bin$ pip list Package Version ---------------------- -------------------- blink…

4.基于多目标粒子群算法冷热电联供综合能源系统运行优化

4.基于多目标粒子群算法冷热电联供综合能源系统运行优化《文章复现》 相关资源代码:基于多目标粒子群算法冷热电联供综合能源系统运行优化 基于多目标算法的冷热电联供型综合能源系统运行优化 考虑用户舒适度的冷热电多能互补综合能源系统优化调度 仿真平台:matl…

微信小程序【TypeError:Cannot read property ‘xxx‘ of undefined】特殊情况解决方法

xxx是一个属性 报错: 解决方法 翻译:TypeError:无法读取未定义的属性“ xxx” 产生原因: 未定义对应的属性变量不能正确的找到对应的变量 解决方法: 原因一: 在data中定义对应变量,并且最…

【51单片机】:定时器的详解(包括对单片机定时解释、各类定时方式,以及中断方式)

学习目标: 51定时/计数器的详解。 码字不易,如有帮助请收藏,点赞哦。 学习内容(背景知识,了解一下对以后学习有帮助): 前提:首先我们知道51单片机内部有21~26个特殊功能寄存器&#…

Linux: 性能分析之On-CPU和Off-CPU

文章目录1. 前言2. 概述3. 分析方法概述3.1 CPU 采样 方法3.2 跟踪 方法4. 使用火焰图分析4.1 On-CPU 分析4.2 Off-CPU 分析4.2.1 Off-CPU 两种分析方法对比4.2.2 生成 Off-CPU 火焰图5. 参考资料1. 前言 限于作者能力水平,本文可能存在谬误,因此而给读…

准备2023(2024)蓝桥杯

前缀和 一维前缀和 s[i]s[i-1]a[i]二维前缀和&#xff08;子矩阵的和&#xff09; s[i][j]s[i-1][j]s[i][j-1]-s[i-1][j-1]a[i][j] 差分 一维数组 //b是差分数组b[i]c;b[j1]-c;例题 #include<iostream> using namespace std; int n,m; int b[100002],a[100002]; vo…

【系统集成项目管理工程师】信息系统集成及服务

&#x1f4a5;信息系统集成及服务 1、信息技术基础架构库&#xff08;ITIL&#xff09; 简介&#xff1a; 最初是为了提高英国政府部门 IT 服务质量而开发&#xff0c;但它很快在英国的各个企业中得到了广泛的应用和认可。 ITIL 包含着如何管理IT 基础设施的流程描述&#xf…