【多线程】CAS、ABA问题详解

一、什么是 CAS

CAS:全称 Compare and swap,字⾯意思:⽐较并交换

比较内存和 CPU 中的内容,如果发现相同,就进行交换

  • 交换的是内存和另一个寄存器的内容

一个内存的数据和两个寄存器中的数据进行操作(寄存器 1 和寄存器 2)

  • 比较内存和寄存器 1 中的值是否相等
  • 如果不相等,就无事发生
  • 如果相等,就交换内存和寄存器 2 的值
    此处我们只关心内存交换后的内容,不关系寄存器 2 交换后的内容,此处虽说叫做“交换”,实际上,希望达成的效果是“给内存赋值

二、CAS 伪代码

并不能执行,只是用来表示一下执行的逻辑

boolean CAS(address, expectValue, swapValue) { if (&address == expectedValue) { &address = swapValue; return true; } return false; 
}
  • address—内存、expectValue—寄存器 1、swapValue—寄存器 2

CAS 的关键不在于这个逻辑是干什么的,而是在于通过“一个 CPU 指令”完成上述一系列的操作

  • 一个 CPU 指令说明这是一个原子性的操作,不怕线程安全
  • 因此,CAS 给我们编写多线程代码,带来了新的思路——“无锁化编程

三、CAS 具体使用场景

1. 基于 CAS 实现“原子类”

int/long 等类型在进行 ++ 的时候,都不是原子的
基于 CAS 实现的原子类,可以看作是对 int/Long 等类型进行了封装,从而可以原子的完成++–`的操作

实现 count++操作:

public class Demo1 {  //private static int count = 0;  private static AtomicInteger count = new AtomicInteger();  public static void main(String[] args) throws InterruptedException {  Thread t1 = new Thread(() -> {  for (int i = 0; i < 5000; i++){  count.getAndIncrement();  //count++  
//              count.incrementAndGet();  //++count  
//              count.decrementAndGet();  //count--  
//              count.getAndDecrement();  //--count  
//              count.getAndAdd(10);//count+=10  }  });        Thread t2 = new Thread(() -> {  for (int i = 0; i < 5000; i++) {  count.getAndIncrement();  }        });        t1.start();  t2.start();  t1.join();  t2.join();  System.out.println(count);  }
}

通过 CAS 实现自增伪代码实现:

class AtomicInteger { private int value; public int getAndIncrement() { //先把内存数据读取到寄存器int oldValue = value; //通过CAS比较内存和寄存器1的值是否一样while ( CAS(value, oldValue, oldValue+1) != true) { oldValue = value; } return oldValue;}
}
  • value—内存数据、oldValue—寄存器的数据
  • 对比 valueoldValue 是否相同,若相同,就意味着没有其他线程穿插到这两个代码之间执行,此时就可以安全地修改变量的内容
    • 将内存的值和 oldValue+1 进行交换(将+1 后的值赋给内存)
  • 如果不相同,那么在上方的赋值和此处的 CAS 之间有其他的线程穿插执行,并且其他的线程修改了 value 的值
    • 这时,就不会进行 `` 交换操作,而是通过 while 循环再读取一次内存中更新的值,再进行是否相同判断
    • 直到相等,完成交换为止

  • 之前的线程安全问题,就是别的线程穿插进来,在本线程修改完毕之前,抢先一步修改了值。
  • 但是此处,通过 CAS 可以感知到是否有修改
  • 直到发现某一次没有人插队,才会进行自增操作

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,则是处于解锁状态
  • Thread.currentThread () —获取到调用 lock 的线程引用
  • this. owner, null 进行比较
    • 若相等,则证明此时这个锁处于解锁状态,把 owner 设为当前调用 lock 的这个线程
    • 若不相等,则证明此时这把锁已经被别的线程持有了,就进行自旋等待,持续循环等待(直到这把锁被解开,使 owner 变为 null

三、ABA 问题

CAS 确实很好用,但也存在很关键的问题—— ABA 问题

什么是 ABA

CAS 之所以能保证线程安全,其中很重要的点就是

  • 在通过 CAS 比较的过程中,可以确定当前是否有其他线程插入进来执行
  • 此处我们是通过判定值是否相同,来区分是否有其他线程修改过
    但值相同 != 没有修改过,因为存在这样的可能,
  • 一个线程将值修改变了
  • 但又有一个线程将值又修改回去了

和“翻新机”是类似的效果,外表看起来和新的一样,但是内部早已是别人的形状
新机 => 别人使用变成旧的机器 => 商家进行翻新之后变成新的机器
A => B => A

CAS 中确实存在 ABA 问题,但是大多数情况下,ABA 问题并不会带来 bug,但有还是有少数情况会产生 bug


一个非常极端的例子:
考虑实现 ATM 的转账功能,转账过程中,通过 CAS 的方式来实现。

void func(int n) {int oldValue = value; //value 就是账户余额if(!(CAS(value, oldValue, oldValue - n))) {System.out.println("转账失败");}else {System.out.println("转账成功");}
}

按照这个逻辑执行取钱操作,此时账户上有 1000 元,我需要转出 500 元
在实际执行取钱动作的时候,由于响应慢,我多按了几下,就导致在 ATM 中出现了多个线程来执行上述逻辑
image.png

如何解决

对于这样的情况,可以通过 CAS 解决——引入“版本号”

ABA 是因为“余额”能加也能减,才会有 ABA 问题

  • 如果只能加,不能减,就能解决问题
  • 但对于余额来说,本身就是能加能减,就不好对它进行限制
  • 我们可以引入“版本号”,是一个整数,但只能增加
void func() {int oldVersion = version; //版本号if(!CAS(version, oldVersion, oldVersion+1)) {转账失败}else{value-=n;转账成功}
}

image.png

四、相关面试题

image.png

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS 多按钮根据半圆弧度排列

需求 多个按钮根据弧度&#xff0c;延边均匀排列。 实现 HTML 分两级&#xff1b;第一级&#xff0c;外层定义按钮的 compose-container 宽度&#xff1b;第二级&#xff0c;按钮集合&#xff0c;使用方法 styleBtn(index)&#xff0c;根据索引计算&#xff1b; <div c…

青岛实训 8月9号 day25

mysql下载路径&#xff1a; MySQL :: MySQL Community Downloads [root2 ~]# vim py001.pya3b4print(ab)print(a**2b**2)[root2 ~]# python py001.py 725[root2 ~]# python3>>> import random>>> random<module random from /usr/lib64/python3.6/random…

vue3、uniapp-vue3模块自动导入

没有使用插件 使用插件,模块自动导入 安装: npm i -D unplugin-auto-importvite.config.js (uniapp没有此文件,在项目根目录下创建) import { defineConfig } from "vite"; import uni from "dcloudio/vite-plugin-uni"; import AutoImport from &qu…

Mask-Rcnn

一 、FPN层 FPN层的基本作用 基本网络架构 基本思想 将多个阶段特征图融合在一起&#xff0c;这就相当于既有了高层的语义特征&#xff0c;也有了低层的轮廓特征 二、RPN层 三、ROI Align层

Java环境安装与配置——eclipse

目录 一、下载安装jdk 二、环境配置 三、下载安装eclipse软件 四、Java命名规则 一、下载安装jdk 1.下载页面 https://www.oracle.com/java/technologies/javase-jdk13-downloads.html 2.下载到本地安装 3.鼠标双击打开 4.选择安装路径并记住位置。建议&#xff1a;最好不…

SQL Zoo 8.Using Null

以下数据均来自SQL Zoo 1.List the teachers who have NULL for their department.&#xff08;列出所属部门为NULL的教师&#xff09; select name from teacher where dept is null 2.Note the INNER JOIN misses the teachers with no department and the departments wit…

JVM -- 类加载器

类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现访问接口和类字节码数据的技术。类加载器只负责加载过程中的字节码获取并加载到内存的这一过程。 一、 类加载器的分类 类加载器的详细信息可以使用Arthas通过classloader命令查看&#xff1a; 1.启动类加载器(Boots…

代码随想录打卡第五十三天

代码随想录–图论部分 day 53 图论第三天 文章目录 代码随想录--图论部分一、卡码网101--孤岛的总面积二、卡码网102--沉没孤岛三、卡码网103--水流问题四、卡码网104--建造最大岛屿 一、卡码网101–孤岛的总面积 代码随想录题目链接&#xff1a;代码随想录 给定一个由 1&…

绘图仪 -- Web前端开发和Canvas绘图

Canvas绘图介绍 Canvas绘图是HTML5中引入的一个非常强大的特性&#xff0c;它允许开发者使用JavaScript在网页上绘制图形、图表、动画等。<canvas>元素提供了一个通过JavaScript和Canvas API进行绘图的环境。 创建绘图仪对象 // 定义一个名为 XYPlotter 的函数&#x…

Mapboxgl 实现弧线功能

更多精彩内容尽在 dt.sim3d.cn &#xff0c;关注公众号【sky的数孪技术】&#xff0c;技术交流、源码下载请添加VX&#xff1a;digital_twin123 代码如下&#xff1a; const mapCenter [-0.5, 51.8];// please use your own token! const map new mapboxgl.Map({container: …

怎样才算精通 Excel?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 高赞回答很系统&#xff0c;但普通人这么学&#xff0c;没等精通先学废了&#xff01; 4年前&#xff0c;我为了学数据分析&#…

iOS Object-C 创建类别(Category) 与使用

有时候使用系统给出类或者第三方的类,但是呢它们自带的属性和方法又太少,不够我们的业务使用,这时候就需要给“系统的类或者第三方类”创建一个类别(Category),把自己的想添加的属性和方法写进来. Category模式用于向已经存在的类添加方法从而达到扩展已有类的目的 一:创建Ca…

继承(二)

隐藏/重定义&#xff1a;子类和父类有同名的成员&#xff0c;子类成员隐藏了父类的成员。 重载&#xff1a;同一个作用域&#xff0c;重载了参数。 &#xff08;在实际中最好不要定义同名函数&#xff09; 子类对象不能初始化父类对象&#xff0c;用父类成员初始化子类成员。…

开关电源之结构分析

如有技术问题及技术需求请加作者微信! 开关电源之结构分析 1、开关电源的结构 常用开关电源,主要是为电子设备提供直流电源供电。电子设备所需要的直流电压,范围一般都在几伏到十几伏,而交流市电电源供给的电压为220V(110V),频率为50Hz(60Hz)。开关电源的作用就是把一…

【Unity】线性代数基础:矩阵、矩阵乘法、转置矩阵、逆矩阵、正交矩阵等

文章目录 矩阵&#xff08;Matrix&#xff09;矩阵能干啥&#xff1f;矩阵基本运算矩阵加减法矩阵和标量的乘法矩阵和矩阵的乘法矩阵的转置矩阵相等 特殊的矩阵方块矩阵对称矩阵对角元素&#xff08;Diagonal Elements&#xff09;对角矩阵&#xff08;Diagonal Matrix&#xf…

C语言-函数

一、函数的概念 其实在C语⾔也引⼊函数&#xff08;function&#xff09;的概念&#xff0c;有些翻译为&#xff1a;⼦程序&#xff0c;⼦程序这种翻译更加准确⼀些。C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。C语⾔的程序其…

hs_err_pid.log分析

hs_err_pid.log 文件是 Java 虚拟机&#xff08;JVM&#xff09;在遇到致命错误&#xff08;如崩溃或内部错误&#xff09;时生成的错误日志文件。这个文件包含了关于崩溃的详细信息&#xff0c;可以帮助开发者或系统管理员诊断和解决问题。 hs_err_pid.log文件位置和命名 文…

基于springboot3实现单点登录(二):认证服务端搭建

前言 上文我们介绍了oauth2.0的相关理论和流程&#xff0c;本文我们继续实现。 Oauth2协议中有个很重要的概念&#xff0c;叫做”端点“&#xff0c; 以下整理了一些常用的端点及其参考访问路径及使用场景的信息&#xff0c;供参考。 这些端点在oauth2.0协议的整个生命周期…

python自动化笔记:操作mysql数据库

操作mysql数据库常见方法 1、第三方库&#xff1a;pymysql1.1、安装pymysql1.2、连接数据库1.3、连接指定数据库1.4 创建数据库、创建表1.5、表中插入数据1.6、批量插入数据1.7、获取查询结果数据1.8、防sql注入&#xff0c;sql语句中一般用占位符传值 2、标准库 &#xff1a;m…

【《Kafka 入门指南:从零基础到精通》】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索KAFKA&#xff0c;本篇文章主要讲述了&#xff1a;消息队列的基础知识&#xff0c;KAFKA消息队列等等。欢迎大家一起探索讨论&#xff01;&#xff01;&#x…