CAS问题

CAS

compare and swap

一个特殊的 CPU 指令 完成的工作,就是 "比较和交换"

这个 cpu 指令本身就是"原子的";

所以基于 cas 指令:给编写线程安全的代码打开了新的大门;

之前线程安全都是靠 加锁 ;导致的结果就是 阻塞===> 性能降低;

使用 CAS ,不仅不涉及加锁,也不会阻塞,合理使用也能保证线程安全(无锁编程)

CAS 本身是 CPU 指令;

操作系统 对 指令 进行了 封装;

jvm 又对 操作系统提供的 api 又封装了一层;

java 中 cas 的 api 放到了 unsafe包(不安全的)里(涉及到一些底层内容,使用不当会带来风险);

java标准库,对于 CAS 又进行了进一步的封装,提供了一些工具类;最主要的一个工具类,叫做 "原子类"

上述代码就处于 "线程不安全"的情况;

解决这个问题的第一个方法就是 给 写操作 加锁;

第二个方法就是 使用原子类;

这里的内容,就没有加锁,也能保证线程安全;

之前的 count++ 是cpu的 三个指令;

多个线程的写操作是三个指令,会相互穿插执行,引起线程不安全;

此处的 getAndIncrement 对变量的修改,是一个 CAS 指令;

一个指令就是原子的;加锁就是为了将三个指令 变成 一个指令 让它成为原子的;

不使用 原子类 的 写操作;

使用了 原子类 的写操作

CAS 中的 ABA 问题

CAS 在使用的时候 关键要点是要 判定当前内存是否和寄存器中的值一样,如果一样就进行修改,不一样就什么都不做

假设有两个线程 A 和 B 同时对同一个共享变量执行 CAS 操作:

线程 A 执行 CAS 操作:

线程 A 从内存读取当前值A,并设定预期值E为A(假设A的值为 10)。

线程 A 计算出新值N(比如N = E + 1,即 11)。

线程 A 执行 CAS 操作,比较内存处的值(也就是A)是否等于预期值E。如果相等,就将内存的值修改为新值N(即把 10 改成 11),操作成功。

线程 B 执行 CAS 操作:

线程 B 同样从内存地址V读取当前值,假设线程 B 读取时,线程 A 还未完成 CAS 操作,所以线程 B 读取到的值也是 10,它也设定预期值E为 10,并计算出新值N(同样为 11)。

当线程 B 执行 CAS 操作时,如果此时线程 A 已经完成了 CAS 操作,那么内存地址V处的值已经变为 11,而线程 B 的预期值E还是 10,两者不相等,线程 B 的 CAS 操作失败,不会修改内存地址V的值。

线程 B 失败后,根据具体的业务逻辑,可以选择重试,即重新读取内存值作为预期值,重新计算新值,再次执行 CAS 操作;也可以选择放弃本次操作,执行其他逻辑。

ABA 问题

存在一种情况:内存 V 存储的数值是 0;

执行CAS 之前,另一个线程把这个值从 0 --> 10 又从 10--> 0;

在其他前程穿插的过程中,将 内存v的值 从 A 改到 B 又改回 A;

此时 CAS 并不知道 修改了值,这种情况就是 : ABA 问题

一般来说是不会出现问题

但对于 ABA 问题来说,什么时候会出现 BUG 呢?

假设,我在银行向另一个账户转账 100元 ,当我按下转账按钮时,没有反应;然后又按了一次;此时产生了两个线程,去尝试进行扣款操作(假定按照 CAS 的方式进行扣款):

线程B 先扣款成功,线程A 发现钱对不上,就不进行操作;

但如果此时在 线程B 扣款成功的同时,线程C 向 我的账户转账了 100元;此时 线程A 就不知道 账户没有转账成功 还是 转账成功,又到账了;

对于 ABA 问题的解决方案:

1)约定数据变化时单向的(只能增加或者只能减少),不能时双向的(又是双向又是单向);

2)对于本身就必须要双向变化的数据(账户转账收款),可以引入一个 版本号-->版本号这个数字就是只能增加,不能减少;(转账1-->收款2--->转账3)

CAS本质上时 JVM 帮我们封装好的;

相关推荐
青云计划10 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿10 小时前
Jsoniter(java版本)使用介绍
java·开发语言
探路者继续奋斗11 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
消失的旧时光-194312 小时前
第十九课:为什么要引入消息队列?——异步系统设计思想
java·开发语言
A懿轩A12 小时前
【Java 基础编程】Java 面向对象入门:类与对象、构造器、this 关键字,小白也能写 OOP
java·开发语言
乐观勇敢坚强的老彭13 小时前
c++寒假营day03
java·开发语言·c++
biubiubiu070613 小时前
谷歌浏览器无法访问localhost:8080
java
大黄说说13 小时前
新手选语言不再纠结:Java、Python、Go、JavaScript 四大热门语言全景对比与学习路线建议
java·python·golang
烟沙九洲13 小时前
Java 中的 封装、继承、多态
java
识君啊13 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端