当前位置: 首页 > news >正文

【JavaEE初阶】CAS(比较和交换)

目录

🌲 什么是 CAS

🌳 CAS的应用

🚩 实现原子类

🚩 实现自旋锁

🎄 CAS 的 ABA 问题

🚩 什么是 ABA 问题

🚩 ABA 问题引来的 BUG

🚩 解决方案

🍀CAS相关面试题


🌲 什么是 CAS

CAS: 全称Compare and swap,字面意思:"比较并交换"

这是一条 cpu 指令,就可以完成比较和交换这样的一套操作,那么对于cpu的一条指令操作,我们知道这是"原子的",这就给我们编写线程安全的代码,打开了新世界的大门。

CAS的流程:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)

  2. 如果比较相等,将 B 写入 V。(交换)

  3. 返回操作是否成功

CAS就类似于我们之前讲的Synchronized关键字,都是将操作变为原子性,但是呢,CAS只适用于一些特殊场景,并不通用

CAS的好处:

  • 当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。

关于CAS是属于我们讲的那一种锁策略?

我们可以将CAS 视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式

🌳 CAS的应用

🚩 实现原子类

标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的.
典型的就是 AtomicInteger 类.

里面提供了很多方法可以供操作

其中的 getAndIncrement 相当于 i++ 操作.

代码:

这就是使用原子类代替了普通int的 i++,此处的代码是没有用到任务加锁操作,那么代码就没有阻塞等待,使代码以更高的效率来执行程序。通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.

🚩 实现自旋锁

自旋锁是基于 CAS 实现更灵活的锁, 获取到更多的控制权.

自旋锁的伪代码如下:

利用上述伪代码的思想就可以实现一个自旋锁了

CAS操作虽然好用,但是也存在一些问题

🎄 CAS 的 ABA 问题

🚩 什么是 ABA 问题

ABA问题:

属于 CAS 的一个重要注意事项。

假设存在两个线程 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, 还是经历了一个变化过程

🚩 ABA 问题引来的 BUG

大部分的情况下, t2 线程这样的一个反复横跳改动, 对于 t1 是否修改 num 是没有影响的. 但是不排除一些特殊情况

接下来我为大家举一个例子:

滑稽老铁进行取款,在取款过程中,发生了bug,按了一下取款,卡了一下紧接着又按了一下取款,此时产生了两个线程来执行上述扣款操作

  • 我们期望一个线程执行 -500 成功,
  • 另一个线程 -500 失败.

此时的代码是没有问题的,我们来引入ABA场景。当我们在取钱的时候(t2扣款完毕),另一个人(t3)正好给滑稽老铁转了500.

  • t2扣款1000->500,t3转载 500->1000(ABA问题 A->B->A)

那么如果t1的执行逻辑在t3之后,此时看到的就是balance仍然是1000,那么t1中的比较就成立了,即t1又会扣款一次,导致取出来500,之际上余额扣了1000,这就是很严重的bug~~

🚩 解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

CAS 操作在读取旧值的同时, 也要读取版本号.

真正修改的时候

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.

  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)

这就好比, 判定这个手机是否是翻新机, 那么就需要收集每个手机的数据, 第一次挂在电商网站上的手机记为版本1,以后每次这个手机出现在电商网站上, 就把版本号进行递增. 这样如果买家不在意这是翻新机, 就买. 如果买家在意, 就可以直接略过

我们再来利用版本号来解决一下滑稽老铁的问题

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

取款:

存款:

在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能.关于 AtomicStampedReference 的具体用法此处不再展开

🍀CAS相关面试题

1.讲解下你自己理解的 CAS 机制

全称 Compare and swap, 即 “比较并交换”. 相当于通过一个原子的操作, 同时完成 “读取内存, 比 较是否相等,修改内存” 这三个步骤. 本质上需要 CPU 指令的支撑

2.ABA问题怎么解决?

给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当 前版本号比之前读到的版本号大, 就认为操作失败

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • OrangePi AIpro学习4 —— 昇腾AI模型应用
  • 代码随想录算法训练营Day33 | 509. 斐波那契数 | 70. 爬楼梯 | 746. 使用最小花费爬楼梯
  • C++——模板进阶
  • 卷积神经网络 - 结构化输出篇
  • 【Linux】编译器gcc/g++ 、程序翻译过程、动静态库
  • springboot引入redis
  • UML建模-测试用例
  • c++的异常处理机制(try、catch、throw)
  • PDF——分割pdf的10个工具
  • 基于STC89C52单片机的U盘设计
  • SVN工作原理和使用示例
  • conda pack迁移环境
  • leetcode-121-买卖股票的最佳时机
  • Java数组1
  • 数据库篇--八股文学习第十七天| 什么是慢查询?原因是什么?可以怎么优化?;undo log、redo log、binlog 有什么用?
  • [译] 怎样写一个基础的编译器
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • git 常用命令
  • IndexedDB
  • Java程序员幽默爆笑锦集
  • Node项目之评分系统(二)- 数据库设计
  • Python 反序列化安全问题(二)
  • python 学习笔记 - Queue Pipes,进程间通讯
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • Spring Cloud Feign的两种使用姿势
  • ubuntu 下nginx安装 并支持https协议
  • Vue ES6 Jade Scss Webpack Gulp
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 关于springcloud Gateway中的限流
  • 前端_面试
  • 前端性能优化--懒加载和预加载
  • 什么是Javascript函数节流?
  • 使用putty远程连接linux
  • 项目实战-Api的解决方案
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • Java数据解析之JSON
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • #{}和${}的区别?
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #pragma 指令
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (C++二叉树05) 合并二叉树 二叉搜索树中的搜索 验证二叉搜索树
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (十一)图像的罗伯特梯度锐化
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)mysql使用Navicat 导出和导入数据库
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .net core控制台应用程序初识
  • .net 发送邮件
  • .NET周刊【7月第4期 2024-07-28】