《Java-SE-第二十八章》之CAS
前言
在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!”
博客主页:KC老衲爱尼姑的博客主页
博主的github,平常所写代码皆在于此
共勉:talk is cheap, show me the code
作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
文章目录
- CAS
- 什么是CAS?
- CAS是怎么实现的
- CAS的应用
- 1. 原子类
- 2. 实现自旋锁
- CAS的ABA问题
- 什么是ABA问题
- ABA问题引来的BUG
- ABA问题复现
- 解决方案
CAS
什么是CAS?
CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:把内存中的某个值和CPU寄存器A中的值,进行比较,如果两个值相同,就把另一个寄存器B中的值个内存的值进行交换,也就是把内存的值放到寄存器B,同时把寄存器B的值写给内存。
CAS 伪代码如下:
boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}
上述伪代码看起来是线程不安全的,实际上是安全的,因为上述 操作都是硬件上提供的原子性的指令完成的。当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。
CAS是怎么实现的
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。简而言之,是因为硬件予以了支持,软件层面才能做到。
CAS的应用
1. 原子类
Java标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的,这些类名都以Atomic开头,针对基础的数据类型进行封装,由于是基于CAS实现的,所以都是线程安全的。
以 AtomicInteger 举例,常见方法有
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i–;
incrementAndGet(); ++i;
getAndIncrement(); i++;
使用演示
两个线程对同一个变量,各自自增5000,使其达到一万,这次不加锁,使用AtomicInteger 来实现
代码如下:
import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo {private static AtomicInteger counter = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i <5000;i++) {counter.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i <5000;i++) {counter.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter);}
}
运行结果:
getAndIncrement ()的伪代码实现
class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}
}
CAS中的i++
假设两个线程同时调用 getAndIncrement
(1) 两个线程都读取 value 的值到 oldValue 中
(2)线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值(value=value+1)
(3)线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环. 在循环里重新读取 value 的值赋给 oldValue
(4)线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作.
- (线程1 和 线程2 返回各自的 oldValue 的值即可.
2. 实现自旋锁
此外基于CSA还可以实现自旋锁
伪代码如下:
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;}
}
上述代码逻辑,如果当前锁对象被线程占用,则lock()方法会 不断的获取锁是否释放,一旦释放了就将owner置为null,然后根据CAS操作将占用该锁的线程设置为当前的线程,并日退出lock()方法,如果是要解锁,就将占用锁对象的线程设置为null。
CAS的ABA问题
什么是ABA问题
通过上述介绍了CAS的操作,该操作最主要的就是先比较,满足条件后交换。但是这存在一个非常极端的情况,假设有2个线程t1和t2,有一个共享变量num,初始值为A,接下里,线程t1想使用CAS把num值修改为Z,由于需要进行CAS操作,就需要先读取num的值,保存到oldNum中m,然后CAS判断当前的num的值是否为A,如果是A,就修改成Z。但是t1执行这两个操作之间,t2线程可能把num的值从A修改成B,又从B修改成A。而线程t1中的CAS的期望啥num的值不变就修改,但是num被t2线程修改了,只不过又改回来了,此时t1是无法判断当前的这个变量始终是A,还是经历了一个变化的过程,那么是否要更新num的值为Z呢。这就是ABA问题,举个栗子,来记忆一下,张三和李四是一对情侣,某一天闹掰了,就分手了,张三在分手期间又找了个女朋友,过了半年又和新的女朋友分手了和李四又在一起了,这个过程中,李四是不知道张三已经移情别恋了。
ABA问题引来的BUG
假设张三有100存款,张三想去ATM取50块钱,取款机创建了2个线程来并发执行这个操作。我们期望一个线程执行成功,另一个线程执行失败,如果使用CAS的方式来来完成这个扣款的过程就会出bug。
正常的过程
- 存款 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 相同, 再次执行扣款操作
此时ATM就会扣款2次,预期结果是扣一次50,结果扣了2次。
ABA问题复现
public class AtomicReferenceDemo {public static AtomicReference<String> ref = new AtomicReference<String>("A");public static void main(String[] args) throws InterruptedException {System.out.println("main start ...");String prev = ref.get();update();Thread.sleep(1000);System.out.println("change A->Z "+ref.compareAndSet(prev, "Z"));}private static void update() throws InterruptedException {new Thread(() -> {System.out.println("change A-B");ref.compareAndSet(ref.get(), "B");},"t1").start();new Thread(() -> {System.out.println("change B-A");ref.compareAndSet(ref.get(), "A");},"t2").start();}
}
运行结果:
解决方案
给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期,CAS 操作在读取旧值的同时, 也要读取版本号,真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).。
在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能.
AtomicStampedReference使用演示
代码如下
import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicStampedReferencedDemo {static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);public static void main(String[] args) throws InterruptedException {System.out.println("main start ...");//获取A的值String prev = ref.getReference();//获取版本号int stamp = ref.getStamp();System.out.println(stamp);update();Thread.sleep(1000);System.out.println("change A->Z "+ref.compareAndSet(prev, "Z",stamp,stamp+1));}private static void update() throws InterruptedException {new Thread(() -> {System.out.println("change A-B");ref.compareAndSet(ref.getReference(), "B",ref.getStamp(), ref.getStamp()+1);System.out.println("t1的版本号:"+ref.getStamp());},"t1").start();new Thread(() -> {System.out.println("change B-A");ref.compareAndSet(ref.getReference(), "A",ref.getStamp(), ref.getStamp()+1);System.out.println("t2的版本号:"+ref.getStamp());},"t2").start();}}
运行结果:
各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。
相关文章:

《Java-SE-第二十八章》之CAS
前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…...

git之reflog分析
写在前面 本文一起看下reflog命令。 1:场景描述 在开发的过程中,因为修改错误,想要通过git reset命令恢复到之前的某个版本,但是选择提交ID错误,导致多恢复了一个版本,假定,该版本对应的内容…...

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(18)-Fiddler如何接口测试,妈妈再也不担心我不会接口测试了
1.简介 Fiddler最大的优势在于抓包,我们大部分使用的功能也在抓包的功能上,fiddler做接口测试也是非常方便的。 领导或者开发给你安排接口测试的工作任务,但是没有给你接口文档(由于开发周期没有时间出接口文档)&…...
Oracle open JDK和 Amazon Corretto JDK的区别
Oracle OpenJDK和Amazon Corretto JDK都是基于Java开放源代码项目的发行版,它们之间有一些区别。 1. 来源:Oracle OpenJDK是由Oracle公司领导和支持的,它是Java的官方参考实现之一。而Amazon Corretto JDK是由亚马逊公司开发和支持的…...

Spark写PGSQL分区表
这里写目录标题 需求碰到的问题格式问题分区问题(重点) 解决完整代码效果 需求 spark程序计算后的数据需要往PGSQL中的分区表进行写入。 碰到的问题 格式问题 使用了字符串格式,导致插入报错。 val frame df.withColumn("insert_t…...
Git 命令行登录
有时候登录命令行版本的git会出现这个错误 1remote: Support for password authentication was removed on August 13, 2021. 2remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for …...

性能分析记录
4实例压测TPS浮动在200-300 1.TPS浮动200-300,ART浮动的可能性是10-20ms,链路复杂是可接受的,链路简单则需要分析原因。 1)缓存没命中,对某些账号缓存没命中,或缓存失效后导致隔段时间耗时升高。 2&…...

Java反射学习(大综合)
第一天 Java反射及动态代理... 2 一、 Java反射... 2 1、什么是反射:... 2 2、反射的原理... 2 3、反射的优缺点:... 2 4、反射的用途:... 3 5、反射机制常用的类:... 3 1、获得Class:主要有三…...

Vite+Vue3 开发UI组件库并发布到npm
一直对开源UI组件库比较感兴趣,摸索着开发了一套,虽然还只是开始,但是从搭建到发布这套流程基本弄明白了,现在分享给大家,希望对同样感兴趣的同学有所帮助。 目前我的这套名为hasaki-ui的组件库仅有两个组件࿰…...

vue- form动态表单验证规则-表单验证
前言 以element官网的form表单的-动态增减表单项为例讲解表单验证规则 动态的功能就是v-model配合push v-for 便利来实现的 我们需要熟知2个知识点prop表单验证需要跟v-model绑定的值是一样的, 如果是一个数组便利的表单,那就需要绑定这个数组每一项…...

FPGA学习—通过数码管实现电子秒表模拟
文章目录 一、数码管简介二、项目分析三、项目源码及分析四、实现效果五、总结 一、数码管简介 请参阅博主以前写过的一篇电子时钟模拟,在此不再赘述。 https://blog.csdn.net/qq_54347584/article/details/130402287 二、项目分析 项目说明:本次项目…...

区块链媒体发稿:区块链媒体宣发常见问题解析
据统计,由于区块链应用和虚拟货币的兴起,越来越多媒体对区块链领域开展报导,特别是世界各国媒体宣发全是热火朝天。但是,随着推卸责任媒体宣发的五花八门,让很多人因而上当受骗,乃至伤害一大笔资产。身为投…...

openGauss学习笔记-28 openGauss 高级数据管理-NULL值
文章目录 openGauss学习笔记-28 openGauss 高级数据管理-NULL值28.1 IS NOT NULL28.2 IS NULL openGauss学习笔记-28 openGauss 高级数据管理-NULL值 NULL值代表未知数据。无法比较NULL和0,因为它们是不等价的。 创建表时,可以指定列可以存放或者不能存…...
DAO和XML文件参数和返回值
①MyBatis中resultType和resultMap的区别 1.使用MyBatis查询数据库记录时,返回类型常用的有两种:resultType和resultMap。那么两者之间有什么区别呢? 如果只是返回一个值,简单类型,比如说String或者int,那…...

vue 浏览器右侧可拖拽小组件
目录 0. 使用场景 1. 动图示例 2. 实现方式 2.1 创建drag.js 2.2 使用v-drag 3. 结尾 0. 使用场景 很多网页在浏览器右侧有"导航"或者“智能助手”的悬浮小气泡框,比如我们的csdn☞ 作为页面友好型的引导标注,某些场景下这些小气泡可以…...
SpringMvc学习笔记五
Restful 风格路由 1. 配置类 1.1、SpringMvcConfig配置类 Configuration ComponentScan({"com.itheima.controller", "com.itheima.config"}) 方式1.2 添加com.itheima.config 扫描目录 EnableWebMvc public class SpringMvcConfig { } 1.2、ServletCo…...
ORACLE-DG总结
述 当主库的某些日志没有成功传送到备库,那么这时候就发生了归档裂缝(Archive Gap)。目前Oracle提供了两种日志GAP的检测和处理机制,分别是自动GAP处理(Automatic Gap Resolution)和FAL进程GAP处理(FAL Gap Resolution)。自动GAP处理即主库上的ARCn进程会每分钟检查备库…...
机器学习中的 K-均值聚类算法及其优缺点
K-均值聚类算法是一种常用的无监督学习算法,用于将相似的数据点分组为聚类。 其步骤如下: 1. 初始化:选择聚类数K,随机选取K个聚类中心。 2. 计算距离:计算每个数据点与K个聚类中心的距离,将其分配到距离最…...
【数据化分析和建模】一般步骤(个人工作经验总结)
近期关于【数据化分析和建模】一般步骤的思考如下。 以终为始,要解决什么问题,实现什么效果? 数据可视化分析的首要目标是通过将数据以可视化图表的形式真实、完整地呈现业务现状,为发现业务问题打好基础,包括实时的…...

视频安防监控EasyCVR平台海康大华设备国标GB28181告警布防的报文说明
TSINGSEE青犀视频监控综合管理平台EasyCVR基于云边端协同,可支持海量视频的轻量化接入与汇聚管理。平台既具备传统安防视频监控的能力,比如:视频监控直播、云端录像、云存储、录像检索与回看、告警上报、平台级联、云台控制、语音对讲等&…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...

高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...
STL 2迭代器
文章目录 1.迭代器2.输入迭代器3.输出迭代器1.插入迭代器 4.前向迭代器5.双向迭代器6.随机访问迭代器7.不同容器返回的迭代器类型1.输入 / 输出迭代器2.前向迭代器3.双向迭代器4.随机访问迭代器5.特殊迭代器适配器6.为什么 unordered_set 只提供前向迭代器? 1.迭代器…...
第21节 Node.js 多进程
Node.js本身是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。 每个子进程总是带有三个流对象:child.stdin, child.stdout和child.stderr。他们可能会共享…...

SpringSecurity+vue通用权限系统
SpringSecurityvue通用权限系统 采用主流的技术栈实现,Mysql数据库,SpringBoot2Mybatis Plus后端,redis缓存,安全框架 SpringSecurity ,Vue3.2Element Plus实现后台管理。基于JWT技术实现前后端分离。项目开发同时采 …...
AIGC 基础篇 Python基础 02
1.bool类型 书接上回,我们上次最后讲了三大数据类型,除了这三个之外,Python也有bool类型,也就是True和False。 a 2 print(a1) print(a2) 像这里,输出的内容第一个是False,因为a的值为2,而第…...
jieba实现和用RNN实现中文分词的区别
Jieba 分词和基于 RNN 的分词在技术路线、实现机制、性能特点上有显著差异,以下是核心对比: 1. 技术路线对比 维度Jieba 分词RNN 神经网络分词范式传统 NLP(规则 统计)深度学习(端到端学习)核心依赖词典…...