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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔记73:ROS中的各种消息包

参考视频: 33.ROS 的标准消息包 std_msgs_哔哩哔哩_bilibili 34. ROS 中的几何包 geometry_msgs 和 传感器包 sensor_msgs_哔哩哔哩_bilibili 标准消息包:std_msgs常用消息包:common_msgs导航消息包:nav_msgs几何消息包&#xf…

Mysql主从备份

主从复制概述 将主服务器的binlog日志复制到从服务器上执行一遍,达到主从数据的一致状态,称之为主从复制。一句话表示就是,主数据库做什么,从数据库就跟着做什么。 为什么要使用主从复制 为实现服务器负载均衡/读写分离做铺垫&…

Cocos Creator 3.8.x 后效处理(前向渲染)

关于怎么开启后效效果我这里不再赘述,可以前往Cocos官方文档查看具体细节:后效处理官网 下面讲一下怎么自己定义一个后处理效果,想添加自己的后效处理的话只需要在postProcess节点下添加一个BlitScreen 组件即可,然后自己去添加自…

CentOS下MySQL的安装以及配置环境

CentOS下MySQL的安装以及配置环境 📟作者主页:慢热的陕西人 🌴专栏链接:MySQL 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要内容讲解了CentOS环境下的…

不同微服务之间如何实现远程调用?一个简单示例告诉你

目录 1、服务拆分原则 2、服务拆分示例 2.1、初始化数据库 2.2、导入demo工程 2.3、测试 3、实现远程调用 3.1、需求 3.2、注册RestTemplate 3.3、实现远程调用 4、提供者与消费者 5、代码免费分享 🍃作者介绍:双非本科大三网络工程专业在读…

【重要公告】对BSV警报系统AS的释义

​​发表时间:2024年2月15日 由BSV区块链协会开发并管理的BSV警报系统(Alert System,以下简称“AS”)是BSV网络的重要组件。它是一个复杂的系统,主要职能是在BSV区块链网络内发布信息。这些信息通常与网络访问规则NAR相…

Flutter中的三棵树

Widget Tree: 页面配置信息。 Element Tree: Widget tree的实例化对象,创建出renderObject,并关联到element.renderobject属性上,最后完成RenderObject Tree的创建。 RenderObject Tree:完成布局和图层绘制…

kettle开发-Day43-加密环境下运行作业

前言: 金三银四,开年第一篇我们来介绍下,怎么在加密情况下运行我们的kettle作业及任务。无疑现在所有企业都认识到加密的重要性,加密后的文件在对外传输的时候不能被访问,访问时出现一堆乱码,同时正常的应用…

租用云服务器租时要注意的问题有哪些?

随着云计算的不断发展,对云计算服务器的需求也越来越大。 那么,我们应该如何以正确的态度和方法来选择云服务器呢? 租用云服务器需要注意哪些问题? 1.了解您需要的云服务类型 了解您的云计算需求将使您了解您正在寻求的服务类型…

管理类联考--复试--英文面试--各校英文面试内容

文章目录 北京地区北京大学中国人民大学北京交通大学北京航空航天大学北方工业大学北京林业大学北京语言大学中央财经大学对外经济贸易大学首都经济贸易大学华北电力大学中国矿业大学中国石油大学北京国家会计学院中国财政科学院研究院北京理工大学北京工商大学中国农业大学 湖…

go并发模式之----工作池/协程池模式

常见模式之四:工作池/协程池模式 定义 顾名思义,就是有固定数量的工人(协程),去执行批量的任务 使用场景 适用于需要限制并发执行任务数量的情况 创建一个固定大小的 goroutine 池,将任务分发给池中的 g…

fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现

项目官网地址:https://fly-barrage.netlify.app/; 👑🐋🎉如果感觉项目还不错的话,还请点下 star 🌟🌟🌟。 Gitee:https://gitee.com/fei_fei27/fly-barrage&a…