详解CAS及ABA问题

news2024/9/22 7:21:17

🌈🌈🌈今天给大家分享的是 CAS 问题。

清风的CSDN博客

🛩️🛩️🛩️希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!

✈️✈️✈️动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!😛😛😛

目录

一、什么是CAS 

1.1 CAS 伪代码  

1.2 CAS是怎么实现的 

二、CAS有哪些应用 

2.1 实现原子类

2.2 实现自旋锁 

三、CAS 的 ABA 问题

3.1 什么是ABA问题

3.2 ABA问题引来的BUG 

3.3 ABA问题的解决方案


一、什么是CAS 

CAS: 全称 Compare and swap ,字面意思 :” 比较并交换 ,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
  • 比较 A 与 V 是否相等。(比较)
  • 如果比较相等,将 B 写入 V。(交换)
  • 返回操作是否成功。

1.1 CAS 伪代码  

下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的。这个伪代码只是辅助理解CAS 的工作流程。
boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false;
}
当多个线程同时对某个资源进行 CAS 操作,只能有一个线程操作成功,但是并不会阻塞其他线程 , 其他线程只会收到操作失败的信号。
CAS 可以视为是一种乐观锁。(或者可以理解成 CAS 是乐观锁的一种实现方式)

1.2 CAS是怎么实现的 

针对不同的操作系统, JVM 用到了不同的 CAS 实现原理,简单来讲:
  • java CAS 利用的的是 unsafe 这个类提供的 CAS 操作
  • unsafe CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg
  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

简而言之,是因为硬件予以了支持,软件层面才能做到 

二、CAS有哪些应用 

2.1 实现原子类

标准库中提供了 java.util.concurrent.atomic , 里面的类都是基于这种方式来实现的。 典型的就是 AtomicInteger 类, 其中的 getAndIncrement 相当于 i++ 操作。
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
① 两个线程都读取 value 的值到 oldValue 中。 (oldValue 是一个局部变量 , 在栈上 . 每个线程有自己的栈 )

② 线程1 先执行 CAS 操作,由于 oldValue value 的值相同, 直接进行对 value 赋值。

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

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

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

 ⑤ 线程1 和 线程2 返回各自的 oldValue 的值即可。

通过形如上述代码就可以实现一个原子类, 不需要使用重量级锁 , 就可以高效的完成多线程的自增操作。

2.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;
   }
}

三、CAS 的 ABA 问题

3.1 什么是ABA问题

假设存在两个线程 t1 t2,  有一个共享变量 num, 初始值为 A,接下来, 线程 t1 想使用 CAS num 值改成 Z, 那么就需要:
  • 先读取 num 的值, 记录到 oldNum 变量中
  • 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z

但是, t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A。

线程 t1 的 CAS 是期望 num 不变就修改, 但是 num 的值已经被 t2 给改了, 只不过又改成 A 了,这个时候 t1 究竟是否要更新 num 的值为 Z 呢?

到这一步, t1 线程无法区分当前这个变量始终是 A, 还是经历了一个变化过程:

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

3.2 ABA问题引来的BUG 

大部分的情况下 , t2 线程这样的一个反复横跳改动 , 对于 t1 是否修改 num 是没有影响的, 但是不排除一些特殊情况。
假设滑稽老哥有 100 存款, 滑稽想从 ATM 取 50 块钱。取款机创建了两个线程, 并发的来执行 -50 操作,我们期望一个线程执行 -50 成功, 另一个线程 -50 失败。
正常的过程
  • 存款 100,线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50。
  • 线程1 执行扣款成功, 存款被改成 50,线程2 阻塞等待中。
  • 轮到线程2 执行了, 发现当前存款为 50, 和之前读到的 100 不相同, 执行失败。
异常的过程
  • 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50
  • 线程1 执行扣款成功, 存款被改成 50,线程2 阻塞等待中
  • 在线程2 执行之前, 滑稽的朋友正好给滑稽转账 50, 账户余额变成 100 
  • 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行扣款操作

 这个时候, 扣款操作被执行了两次!!! 都是 ABA 问题搞的鬼!!

3.3 ABA问题的解决方案

给要修改的值 , 引入版本号,   CAS 比较数据当前值和旧值的同时 , 也要比较版本号是否符合预期
  • CAS 操作在读取旧值的同时, 也要读取版本号
  • 真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1。
  • 如果当前版本号高于读到的版本号,就操作失败(认为数据已经被修改过了)。
这就好比, 判定这个手机是否是翻新机, 那么就需要收集每个手机的数据, 第一次挂在电商网站上的手机记为版本1, 以后每次这个手机出现在电商网站上, 就把版本号进行递增,这样如果买家不在意这是翻新机, 就买,如果买家在意, 就可以直接略过。

 对比理解上面的转账例子:

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

🌈🌈🌈好啦,今天的分享就到这里!

🛩️🛩️🛩️希望各位看官读完文章后,能够有所提升。

🎉🎉🎉创作不易,还希望各位大佬支持一下!

✈️✈️✈️点赞,你的认可是我创作的动力!

⭐⭐⭐收藏,你的青睐是我努力的方向!

✏️✏️✏️评论:你的意见是我进步的财富!

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

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

相关文章

【工具栏】SequenceDiagram插件的使用(根据代码生成时序图)

1. 安装 2.使用 进入代码页面,点击鼠标右键 选择方法 根据方法中的代码生成时序图

【LangChain学习之旅】—(8) 输出解析:用OutputParser生成鲜花推荐列表

【LangChain学习之旅】—(8) 输出解析:用OutputParser生成鲜花推荐列表 LangChain 中的输出解析器Pydantic(JSON)解析器实战第一步:创建模型实例第二步:定义输出数据的格式第三步:创…

AI-数学-高中-4.函数表达式特性-要变一起变

求f(x):换元法:左边代换时,右边也要同时替换,原作者视频:函数】1引导课:高中为什么用f(x)_哔哩哔哩_bilibili 1.什么是函数:给定任意一个x,都有唯一确定的y与之对应,这种x与y的关系就…

猫头虎分享:探索TypeScript的世界 — TS基础入门 ‍

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通Golang》…

UML-类图和类图转化为代码

提示:文章详细的讲解了类图的四种关系,以及每种关系如何转化为对应的代码。 UML-类图和类图转化为代码 一、类于类之间的关系1.依赖关系2.关联关系(1) 单向关联(2) 双向关联(3) 自关联(4) 聚合关联(has-a)(5) 组合关联(contains-a&#xff09…

当浏览器输入url的时候会发生什么?

说在前面 当我们在浏览器中输入URL并按下回车时,背后发生了一系列神秘的操作。本文将带您深入了解,从URL解析到页面渲染,揭秘浏览器输入URL的完整流程。 具体步骤 当浏览器输入URL时,一般经过以下细节步骤: 1、引言 …

【GDAL】Windows下VS+GDAL开发环境搭建

Step.0 环境说明(vs版本,CMake版本) 本地的IDE环境是vs2022,安装的CMake版本是3.25.1。 Step.1 下载GDAL和依赖的组件 编译gdal之前需要安装gdal依赖的组件,gdal所依赖的组件可以在官网文档找到,可以根据…

【学习心得】Git深入学习

若您还未安装Git或是只想简单使用,可以先看看我的文章“Git快速上手”http://t.csdnimg.cn/gsaGj 一、深入学习Git必须熟悉两个概念 (1)【四个区】Git本地有三个区,远程仓库也可以看出成一个区域 工作区、暂存区、本地仓库、远…

AI教我学编程之C#变量及实例演示

前言 在AI教我学编程之AI自刀 这篇文章中,我们知道了变量的基础类型,那么变量在C#中有什么作用呢?我们一起来看看吧! 目录 重点先知 变量 变量类型 实例演示 变量声明 实例演示 提出疑问 初始化变量 自动初始化 多变量声明 实…

关于浏览器缓存的一些坑记录

1.js强制刷新浏览器缓存 网络上一搜基本都说用location.reload(true)可以强制刷新缓存,但是没人讲这个方法只准对火狐浏览器生效,火狐浏览器的确可以实现强制刷新并每次请求都是新的没有走缓存;chrome依然是走200 状态 缓存。 MDN解析: 2.m…

Python教程(23)——Python类中常用的特殊成员

在Python中,类特殊成员是指以双下划线开头和结尾的属性和方法,也被称为魔术方法(Magic methods)或特殊方法(Special methods)。这些特殊成员在类的定义中具有特殊的语法和功能,用于实现对象的特…

【python入门】day26: 模拟高铁售票系统

界面 代码 #-*- coding:utf-8 -*- import prettytable as pt#---------导入漂亮表格 import os.path filename ticket.txt#更新座位状态 def update(row_num):#------更新购票状态with open(filename,w,encodingutf-8) as wfile:for i in range(row_num):lst1 [f{i1},有票,有…

学习回顾总结

简述 哈喽大家好,记得上一次发文是在2020年的8月,当时是在西工大的长安校区做实验,在那里写了上一篇博客基于STM32f103ZET6单片机按键控制电机正反转(带PWM调速)。时间过得飞快,现在是2024年的1月&#xff…

【STM32】HAL库的RCC复位状态判断及NVIC系统软件复位

【STM32】HAL库的RCC复位状态判断及NVIC系统软件复位 在实际开发中 有时候会遇到复位状态不同 导致结果不同的情况 比如在上电复位时 电压不稳定 可能导致一些外部芯片无法正常工作 从而导致进行了错误的操作流程 所以 可以在程序运行后 加一个复位状态判断 用来检测是否正常复…

【代码随想录04】24. 两两交换链表中的节点 19. 删除链表的倒数第 N 个结点 面试题 02.07. 链表相交 142. 环形链表 II

24. 两两交换链表中的节点 题目描述 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 做题思路 可以设置虚拟头结点cur和画图…

Java 使用 JDBC 连接mysql

之前我们学习了JavaSE,编写了Java程序,数据保存在变量、数组、集合等中,无法持久化,后来学习了IO流可以将数据写入文件,但不方便管理数据以及维护数据的关系; 后来我们学习了数据库管理软件MySQL&#xff…

POI-tl 知识整理:整理1 -> 利用模板向word中写入数据

1 文本传值 Testpublic void testText() throws Exception {XWPFTemplate template XWPFTemplate.compile("D:\\Idea-projects\\POI_word\\templates.docx");Map<String, Object> map new HashMap<>();map.put("title", "Hi, girl"…

针对大规模服务日志敏感信息的长效治理实践

文章目录 1 背景2 目标与措施3 实施3.1 脱敏工具类3.2 JSON脱敏3.3 APT自动脱敏3.3.1 本地缓存问题3.3.2 JDK序列化问题 3.4 弃用方案 4 规划5 总结 1 背景 近年来&#xff0c;国家采取了多项重要举措来加强个人数据保护&#xff0c;包括实施《中华人民共和国网络安全法》和《…

JAVA毕业设计632—基于Java+ssm的宠物店商城系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于Javassm的宠物店商城系统(源代码数据库)632 一、系统介绍 本项目分为用户、营养师、管理员三种角色 1、用户&#xff1a; 登录、注册、宠物信息、宠物粮食、宠物用品、宠物疫…

imx6ull基于yocto工程的l汇编点亮ed

通过汇编点亮led 在裸机状态下通过汇编点亮led&#xff0c;即没有操作系统&#xff0c;(uboot kernel rootfs 都不需要实现&#xff09;。 led点亮原理 1.GPIO复用 根据原理图&#xff0c;找到led对应的引脚&#xff08;pin)&#xff0c;复用为GPIO&#xff08;只有GPIO才能…