无锁并发:探秘CAS机制的魔力

目录
- 1、概念
- 2、原理
- 3、缺点
- 4、ABA问题
- 5、解决ABA问题
1、概念
CAS(Compare And Swap): 比较并替换,它是一条CPU原语,是一条原子指令(原子性)。
CAS通过比较真实值与预期值是否相同,如果是则进行修改,Atomic原子类底层就是使用了CAS,CAS属于乐观锁。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么会自动将该内存位置的值更新为新值,反之不做任何操作。
2、原理
CAS执行依赖于Unsafe类,Unsafe类的所有方法通过native修饰,所以Unsafe类直接操作系统底层的数据地址,而不通过JVM实现。
比如:A、B线程通过AtomicInteger同时对变量进行自增
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);}// 使用了unsafe类public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {// 获取原值var5 = this.getIntVolatile(var1, var2);// 将原值与预期值进行对比,一致则进行相加var4} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}
伪代码流程解释:
A线程将变量num从1进行自增,当执行时没有其他线程修改过num值,所以一次成功,num自增后将值返回给主内存。
B线程也将num进行自增,但是num已经被A线程进行了修改,所有将再次执行do-while,重新获取num的值。
由于JMM模型的可见性,B线程重新获取值时会从主内存中获取到被A修改后的最新值。
通过最新值与期望值进行比较,满足条件就返回true。
CAS如何实现:
通过Unsafe类对系统底层的数据地址进行原子性操作,对比内存地址的值和预期值是否一样,如果一样进行修改。
3、缺点
- 1、循环时间长,增加了CPU的开销。
- 2、只能保证一个变量的原子操作。
- 3、会导致ABA问题。
4、ABA问题
如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的,因为内存X中的值从A到B再到了A。
CAS执行时,将过去某时刻的值与当下时刻进行比较并替换,在这时间差中,值可能会发生多次修改,只是最终值的结果不变。
5、解决ABA问题
为了防止在值比较时,存在被修改过的可能,通过为值加上版本号的方式,在最后执行CAS时判断版本号,确保不会出现ABA问题。
通过原子引用(AtomicReference)加时间戳原子引用(AtomicStampedReference)解决ABA问题。
代码如下:
public class Test {private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {System.out.println("=========以下ABA问题产生:=========");// 线程 t1 模拟ABA问题的产生new Thread(() -> {// 进行一次修改,将值修改为101atomicReference.compareAndSet(100, 101);// 进行第二次修改,将值修改回100atomicReference.compareAndSet(101, 100);}, "t1").start();new Thread(() -> {// 线程t2暂停1S,保证线程t1执行完成try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}System.out.println(atomicReference.compareAndSet(100, 102));System.out.println("修改成功,修改后值为:"+atomicReference.get());}, "t2").start();try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}System.out.println("=========以下ABA问题解决方式:=========");new Thread(() -> {// 获取版本号int stamp = atomicStampedReference.getStamp();System.out.println("线程名称:"+Thread.currentThread().getName()+",第一次版本号:"+stamp);try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}// 进行一次修改,将值修改为101atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);stamp = atomicStampedReference.getStamp();System.out.println("线程名称:"+Thread.currentThread().getName()+",第二次版本号:"+stamp);// 进行第二次修改,将值修改回100atomicStampedReference.compareAndSet(101,100,stamp,stamp+1);stamp = atomicStampedReference.getStamp();System.out.println("线程名称:"+Thread.currentThread().getName()+",第三次版本号:"+stamp);}, "t3").start();new Thread(() -> {// 获取版本号int stamp = atomicStampedReference.getStamp();System.out.println("线程名称:"+Thread.currentThread().getName()+",第一次版本号:"+stamp);// 线程t4暂停1S,保证线程t3执行完成try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}boolean andSet = atomicStampedReference.compareAndSet(100, 101, stamp,stamp+1);System.out.println("线程名称:"+Thread.currentThread().getName()+",修改结果:"+andSet+",第二次版本号:"+stamp+1+"实际版本号:"+atomicStampedReference.getStamp());System.out.println("当前最新值:"+atomicStampedReference.getReference());}, "t4").start();}
}
分析:
在线程t1中,将值从100修改为了101又修改回了100,但是线程t2却成功更新了值,所以产生了ABA问题。
在线程t3中,将值从100修改为了101又修改回了100,并且更新的版本号为3;在线程t4中,将100修改为101时,由于线程t4更新时的版本号为2,但是实际的版本号为3,所以无法更新,解决了ABA问题。
运行结果:


相关文章:
无锁并发:探秘CAS机制的魔力
😊 作者: 一恍过去 💖 主页: https://blog.csdn.net/zhuocailing3390 🎊 社区: Java技术栈交流 🎉 主题: 无锁并发:探秘CAS机制的魔力 ⏱️ 创作时间: 2…...
iOS App签名与重签名:从开发者证书到重新安装运行
前文回顾: iOS脱壳技术(二):深入探讨dumpdecrypted工具的高级使用方法 iOS逆向:越狱及相关概念的介绍 在本文中,我们将详细介绍iOS应用的签名过程,包括开发者证书的种类、证书与App ID、Provisi…...
vue项目,如何修改Element-Plus等UI组件库的样式,三种方式搞定!!!
前言 我们在学习和使用组件库构建页面的时候,时常会遇到这样的问题。 即,尽管组件库已经提供了较多的功能,来帮助我们构建自定义的效果,但有时仍不能使我们满意。 这个时候我们就不得不修改UI库的样式,来达到想要的状…...
httpd协议与apache
1.http 相关概念 HTTP是处于应用层的协议,使用TCP传输层协议进行可靠的传送。因此,需要特别提醒的是,万维网是基于因特网的一种广泛因特网应用系统,且万维网采用的是HTTP(80/TCP)和 HTTPS(443/…...
Go 自学:文件的写入和读取
首先,使用os.Create()函数建立一个文件。 接着,使用io.WriteString()函数将内容写入文件。 最后,使用os.ReadFile()函数读取文件内容。 注意,这里读取的文件内容是data byte,我们需要使用string()函数将其转换为字符串…...
py 项目上线centos
1 服务器py版本 ps -ef|grep python|grep -v grep 2 2.x版本 安装 PyMySQL pip install PyMySQL0.9.3 3 后台运行py文件 nohup python down.py 1 > log.log 2>&1 & 这个命令将 down.py 程序放入后台运行, 同时将 stdout 输出到 log.log 文件中&…...
【git】would clobber existing tag 报错解决
问题 在用vscode的Git去pull代码的时候git弹窗报错,查看报错日志发现以下内容: > git pull --tags origin feature/xxx-2.0.0 From 173.110.11.22:VV-WORK-FE/vv-desktop* branch feature/xxx-2.0.0 -> FETCH_HEAD! [rejected] …...
Python OCR 使用easyocr库将图片中的文章提取出来
Python OCR 使用easyocr库将图片中的文章提取出来 初环境内容步骤一:安装easyocr库步骤二:导入必要的库步骤三:创建OCR阅读器对象步骤四:指定要识别的图片路径步骤五:执行OCR识别并提取文章内容步骤六:遍历…...
门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据
环境: ivms-4200 v3.10.0.6_c 问题描述: 门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据,旧电脑记住密码,忘了密码和密保了 解决方案: 1.前往海康官网下载4200客户端,在新电脑上安装 …...
初试Eureka注册中心
Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。一个Eureka中分为eureka server和eureka client。其中eureka server是作为服务的注册与发现中心。 搭建eureka服务 引入eureka依赖 引入SpringCloud为eureka提供的starter依…...
【趣味随笔】怎么维护自己的电脑?
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...
element 下拉组件获取对象
// 选择数据user:[{name:"小白",id:1,money:"100",love:"蛋糕"},{name:"小黑",id:2,money:"200",love:"奶茶"},{name:"小红",id:3,money:"300",love:"烧烤"},] <div><el…...
IDEA下SpringBoot指定环境、配置文件启动
1、idea下的SpringBoot启动:指定配置文件 Springboot项目有如下配置文件 主配置文件application.yml, 测试环境:application-test.yml 生产环境:application-pro.yml 开发环境:application-dev.yml 1.1.配置文件…...
python可视化matplotlib——绘制正弦和余弦
这是一个使用matplotlib库绘制正弦和余弦函数曲线的代码示例。代码中导入了需要的库,并设置了x轴和y轴的标签字体为华文楷体。然后,使用numpy生成一组x轴上的值t,并使用正弦函数生成对应的y轴值s,再使用余弦函数生成对应的y轴值z。…...
Day48|leetcode 198.打家劫舍、213.打家劫舍II、打家劫舍|||
leetcode 198.打家劫舍 题目链接:198. 打家劫舍 - 力扣(LeetCode) 视频链接:动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili 题目概述 你是一个专业的小偷,…...
Mysql001:Mysql概述以及安装
前言:本课程将从头学习Mysql,以我的工作经验来说,sql语句真的太重要的,现在互联网所有的一切都是建立在数据上,因为互联网的兴起,现在的数据日月增多,每年都以翻倍的形式增长,对于数…...
如何调用api接口获取到商品数据
要调用API接口获取商品数据,需要进行以下步骤: 1.确定API接口 首先需要确定要使用的API接口,可以通过搜索引擎或者相关文档来查找适合的API接口。以淘宝开放平台为例,可以使用淘宝的商品信息查询API接口来获取商品数据。 2.注册…...
http请求方式过滤器与拦截器的区别
get:获取查询数据(查询)post:数据的提交,新增操作(增加)put:向服务端发送数据、改变信息,侧重点在于对数据的修改操作delete:数据库数据的删除head:一般用来判断类型、根据返回状态确定资源是否存在、资源是否更新以及更新的时间等 过滤器与拦截器的区别…...
大语言模型初学者指南 (2023)
大语言模型 (LLM) 是深度学习的一个子集,它正在彻底改变自然语言处理领域。它们是功能强大的通用语言模型,可以针对大量数据进行预训练,然后针对特定任务进行微调。这使得LLM能够拥有大量的一般数据。如果一个人想将LLM用于特定目的ÿ…...
日常生活小技巧 -- 单位换算
开发过程中经常需要需要单位换算的地方。 可以使用工具进行转换: 工具:单位转换 常用单位: 1、角度转换 1弧度(rad) 180/PI 度(deg) 57.29577951308232 度(deg) 1度…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
🧠 LangChain 中 TextSplitter 的使用详解:从基础到进阶(附代码) 一、前言 在处理大规模文本数据时,特别是在构建知识库或进行大模型训练与推理时,文本切分(Text Splitting) 是一个…...
