【多线程】CAS详解

目录

  • 🌴什么是 CAS
    • 🌸CAS 伪代码
  • 🎍CAS 是怎么实现的
  • 🍀CAS 有哪些应⽤
    • 🌸实现原子类
    • 🌸实现自旋锁
  • 🌳CAS 的 ABA 问题
    • 🌸**什么是 ABA 问题**?
    • 🌸ABA 问题引来的 BUG
    • 🌸解决方案
  • ⭕相关面试题
  • 🚩总结

🌴什么是 CAS

CAS: 全称Compare and swap字⾯意思:”⽐较并交换“,⼀个 CAS 涉及到以下操作:

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

  1. ⽐较 A 与 V 是否相等。(⽐较)
  2. 如果⽐较相等,将 B 写⼊ V。(交换)
  3. 返回操作是否成功。

🌸CAS 伪代码

下⾯写的代码不是原⼦的, 真实的 CAS 是⼀个原⼦的硬件指令完成的. 这个伪代码只是辅助理解 CAS的⼯作流程.

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
 &address = swapValue;
 return true;
 }
 return false;
}

下面看两种典型的不是原子性的代码:

  1. check and set (if 判定然后设定值) [上⾯的 CAS 伪代码就是这种形式]
  2. read and update (i++) [之前我们讲线程安全的代码例⼦是这种形式]

CAS原子性的作用

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

所以

CAS 可以视为是⼀种乐观锁. (或者可以理解成 CAS 是乐观锁的⼀种实现⽅式)

🎍CAS 是怎么实现的

针对不同的操作系统,JVM ⽤到了不同的 CAS 实现原理,简单来讲:

  • java 的 CAS 利⽤的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
  • Atomic::cmpxchg 的实现使⽤了汇编的 CAS 操作,并使⽤ cpu 硬件提供的 lock 机制保证其原⼦
    性。

简⽽⾔之,是因为硬件予以了⽀持,软件层⾯才能做到

🍀CAS 有哪些应⽤

🌸实现原子类

标准库中提供了 java.util.concurrent.atomic 包, ⾥⾯的类都是基于这种⽅式来实现的.
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作.

AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();

伪代码实现:

class AtomicInteger {
	 private int value;
	 
	 public int getAndIncrement() {
		 int oldValue = value;
		 while ( CAS(value, oldValue, oldValue+1) != true) {
		 	oldValue = value;
		 }
		 return oldValue;
	 }
 }

假设两个线程同时调⽤ getAndIncrement

  1. 两个线程都读取 value 的值到 oldValue 中. (oldValue 是⼀个局部变量, 在栈上. 每个线程有⾃⼰的
    栈)
    在这里插入图片描述
  2. 线程1 先执⾏ CAS 操作. 由于 oldValue 和 value 的值相同, 直接进⾏对 value 赋值.

注意:
• CAS 是直接读写内存的, ⽽不是操作寄存器.
• CAS 的读内存, ⽐较, 写内存操作是⼀条硬件指令, 是原⼦的.

在这里插入图片描述
3. 线程2 再执⾏ CAS 操作, 第⼀次 CAS 的时候发现 oldValue 和 value 不相等, 不能进⾏赋值. 因此需要进⼊循环.

在循环⾥重新读取 value 的值赋给 oldValue
在这里插入图片描述
4. 线程2 接下来第⼆次执⾏ CAS, 此时 oldValue 和 value 相同, 于是直接执⾏赋值操作.

在这里插入图片描述
5. 线程1 和 线程2 返回各⾃的 oldValue 的值即可.

通过形如上述代码就可以实现⼀个原⼦类. 不需要使⽤重量级锁, 就可以⾼效的完成多线程的⾃增操作.

本来 check and set 这样的操作在代码⻆度不是原⼦的. 但是在硬件层⾯上可以让⼀条指令完成这个
操作, 也就变成原⼦的了.

🌸实现自旋锁

基于 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;
 }
}

🌳CAS 的 ABA 问题

🌸什么是 ABA 问题

通过下面的例子理解ABA问题:

假设存在两个线程 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 是没有影响的. 但是不排除⼀些特殊情况.

假设 滑稽⽼哥 有 100 存款. 滑稽想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执⾏ -50 操 作.
我们期望⼀个线程执⾏ -50 成功, 另⼀个线程 -50 失败. 如果使⽤ CAS 的⽅式来完成这个扣款过程就可能出现问题.

正常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更
    新为 50.
  2. 线程1 执⾏扣款成功, 存款被改成 50. 线程2 阻塞等待中.
  3. 轮到线程2 执⾏了, 发现当前存款为 50, 和之前读到的 100 不相同, 执⾏失败.

异常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更
    新为 50.
  2. 线程1 执⾏扣款成功, 存款被改成 50. 线程2 阻塞等待中.
  3. 在线程2 执⾏之前, 滑稽的朋友正好给滑稽转账 50, 账⼾余额变成 100 !!
  4. 轮到线程2 执⾏了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执⾏扣款操作

这个时候, 扣款操作被执⾏了两次!!! 都是 ABA 问题搞的⻤!!

🌸解决方案

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

CAS 操作在读取旧值的同时, 也要读取版本号.
• 真正修改的时候,

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
  • 如果当前版本号⾼于读到的版本号. 就操作失败(认为数据已经被修改过了).

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

对⽐理解上⾯的转账例子

假设 滑稽⽼哥 有 100 存款. 滑稽想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执⾏ -50 操 作.
我们期望⼀个线程执⾏ -50 成功, 另⼀个线程 -50 失败. 为了解决 ABA 问题, 给余额搭配⼀个版本号, 初始设为 1.

  1. 存款 100. 线程1 获取到 存款值为 100, 版本号为 1, 期望更新为 50; 线程2 获取到存款值为 100, 版本
    号为 1, 期望更新为 50.
  2. 线程1 执⾏扣款成功, 存款被改成 50, 版本号改为2. 线程2 阻塞等待中.
  3. 在线程2 执⾏之前, 滑稽的朋友正好给滑稽转账 50, 账⼾余额变成 100, 版本号变成3.
  4. 轮到线程2 执⾏了, 发现当前存款为 100, 和之前读到的 100 相同, 但是当前版本号为 3, 之前读到的版本号为 1, 版本⼩于当前版本, 认为操作失败.

在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进⾏包装, 在内
部就提供了上⾯描述的版本管理功能.

关于 AtomicStampedReference 的具体⽤法此处不再展开. 有需要的同学⾃⾏查找⽂档了
解使⽤⽅法即可.

⭕相关面试题

  1. 讲解下你⾃⼰理解的 CAS 机制

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

2.ABA问题怎么解决?

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

🚩总结

关于《【多线程】CAS详解》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

你心中的韩剧TOP1是哪一部

关注公众号:萌番bilfun,发送影片名称,即可获取资源链接 【2024最新韩剧来袭,准备好迎接心灵的震撼了吗?】 韩剧迷们,你们期待已久的2024最新韩剧终于来了!准备好迎接心灵的震撼了吗&#xff1f…

【嵌入式学习】网络编程day03.02

一、项目 1、TCP机械臂测试 #include <myhead.h> #define SER_IP "192.168.126.32" #define SER_PORT 8888 #define CER_IP "192.168.126.42" #define CER_PORT 9891 int main(int argc, const char *argv[]) {int wfd-1;//创建套接字if((wfdsocke…

【PyTorch】成功解决AttributeError: ‘Tuple‘ object has no attribute ‘cuda‘

【PyTorch】成功解决AttributeError: ‘Tuple‘ object has no attribute ‘cuda‘ &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&…

Vue.js大师: 构建动态Web应用的全面指南

VUE ECMAScript介绍什么是ECMAScriptECMAScript 和 JavaScript 的关系ECMAScript 6 简介 ES6新特性let基本使用const不定参数箭头函数对象简写模块化导出导入a.jsb.jsmain.js Vue简介MVVM 模式的实现者——双向数据绑定模式 Vue环境搭建在页面引入vue的js文件即可。创建div元素…

分享Selenium测试工具用来模拟用户浏览器的操作

执行JS的类库&#xff1a;execjs&#xff0c;PyV8&#xff0c;selenium&#xff0c;node pip list pip install selenium pip install xlrd pip install xlwt pip install PyExecJS pip install xlutils selenium测试工具可以用来模拟用户浏览器的操作&#xff0c;其支持的浏览…

ssm172旅行社管理系统的设计与实现

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一 、设计说明 1.1 研究…

【InternLM 笔记】使用InternStudio 体验书生·浦语2-chat-1.8b随记

书生浦语2-chat-1.8b 介绍 书生浦语-1.8B (InternLM2-1.8B) 是第二代浦语模型系列的18亿参数版本。为了方便用户使用和研究&#xff0c;书生浦语-1.8B (InternLM2-1.8B) 共有三个版本的开源模型&#xff0c;他们分别是&#xff1a; InternLM2-1.8B: 具有高质量和高适应灵活性…

CSP-201712-2-游戏

CSP-201712-2-游戏 解题思路 初始化变量&#xff1a;定义整数变量n和k&#xff0c;分别用来存储小朋友的总数和淘汰的特定数字。然后定义了num&#xff08;用来记录当前报的数&#xff09;和peopleIndex&#xff08;用来记录当前报数的小朋友的索引&#xff09;。 初始化小朋…

什么是VR虚拟社区|VR元宇宙平台|VR主题馆加盟

VR虚拟社区是指一种基于虚拟现实技术构建的在线社交平台或环境&#xff0c;用户可以在其中创建虚拟化的个人形象&#xff08;也称为avatars&#xff09;并与其他用户进行交流、互动和合作。在VR虚拟社区中&#xff0c;用户可以选择不同的虚拟场景和环境&#xff0c;如虚拟公园、…

autocrlf和safecrlf

git远程拉取及提交代码&#xff0c;windows和linux平台换行符转换问题&#xff0c;用以下两行命令进行配置&#xff1a; git config --global core.autocrlf false git config --global core.safecrlf true CRLF是windows平台下的换行符&#xff0c;LF是linux平台下的换行符。…

揭示 Wasserstein 生成对抗网络的潜力:生成建模的新范式

导 读 Wasserstein 生成对抗网络 (WGAN) 作为一项关键创新而出现&#xff0c;解决了经常困扰传统生成对抗网络 (GAN) 的稳定性和收敛性的基本挑战。 由 Arjovsky 等人于2017 年提出&#xff0c;WGAN 通过利用 Wasserstein 距离彻底改变了生成模型的训练&#xff0c;提供了一个…

如何在群晖Docker运行本地聊天机器人并结合内网穿透发布到公网访问

文章目录 1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 随着ChatGPT 和open Sora 的热度剧增,大语言模型时代,开启了AI新篇章,大语言模型的应用非常广泛&#xff0c;包括聊天机…

Tokenize Anything via Prompting论文解读

文章目录 前言一、摘要二、引言三、模型结构图解读四、相关研究1、Vision Foundation Models2、Open-Vocabulary Segmentation3、Zero-shot Region Understanding 五、模型方法解读1、Promptable TokenizationPre-processingPromptable segmentationConcept predictionZero-sho…

STM32标准库开发—实时时钟(BKP+RTC)

BKP配置结构 注意事项 BKP基本操作 时钟初始化 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);//设置PWR_CR的DBP&#xff0c;使能对PWR以及BKP的访问读写寄存器操作 uint16_t ArrayW…

LeetCode--72

72. 编辑距离 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 示例 1&#xff1a; 输入&#xff1a;word1 "horse", word2 …

Mysql与StarRocks语法上的不同

&#x1f413; 序言 StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;可以用StarRocks 来支持多种数据分析场景的极速分析。 &#x1f413; 语法…

STL容器之string类

文章目录 STL容器之string类1、 什么是STL2、STL的六大组件3、string类3.1、string类介绍3.2、string类的常用接口说明3.2.1、string类对象的常见构造3.2.2、string类对象的容量操作3.2.3、string类对象的访问及遍历操作3.2.4、 string类对象的修改操作3.2.5、 string类非成员函…

springBoot整合Redis(二、RedisTemplate操作Redis)

Spring-data-redis是spring大家族的一部分&#xff0c;提供了在srping应用中通过简单的配置访问redis服务&#xff0c;对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装&#xff0c;RedisTemplate提供了redis各种操作、异常处理及序列化&#xff0c;支持发布订阅&…

支持向量机算法(带你了解原理 实践)

引言 在机器学习和数据科学中&#xff0c;分类问题是一种常见的任务。支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种广泛使用的分类算法&#xff0c;因其出色的性能和高效的计算效率而受到广泛关注。本文将深入探讨支持向量机算法的原理、特点、应用&…

Unity(第二十一部)动画的基础了解(感觉不了解其实也行)

1、动画组件老的是Animations 动画视频Play Automatically 是否自动播放Animate Physics 驱动方式&#xff0c;勾选后是物理驱动Culling Type 剔除方式 默认总是动画化就会一直执行下去&#xff0c;第二个是基于渲染播放&#xff08;离开镜头后不执行&#xff09;&#xff0c; …