CAS的超~详细介绍

什么是CAS

CAS全称Compare and swap,是一种比较特殊的CPU指令.  字面意思:"比较并交换",

一个CAS涉及到以下操作:

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

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

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

3.返回操作是否成功.

伪代码

下面写的代码不是原子的,真实的CAS是一个原子的硬件指令完成的.这个伪代码只是辅助理解CAS的工作流程.

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

其中,address表示内存地址,expectValue和swapValue表示寄存器中的值.

流程就是比较address内存地址中的值是否与expect寄存器中的值相同.如果相同,就把swap寄存器中的值和address中的值进行交换(说是交换,实际上是赋值,其实完了之后我们往往只关注内存中的值,寄存器中的值,就不需要了),返回true.     如果不同,无事发生,返回false. 

CAS是怎么实现的

针对不同的操作系统,JVM用到了不同的CAS实现原理(操作系统对指令进行封装,JVM又对操作系统提供的api又封装了一层.),简单来讲:

java的CAS利用的是unsafe这个类提供的CAS操作;(这样的操作,涉及到一些系统底层内容,使用不当,可能会带来风险,一般不建议直接用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;
        //发现value与oldValue不同.意味着CAS之前另一个线程修改了value
        //通过该方式,能意识到被修改
        while ( CAS(value, oldValue, oldValue + 1) != true) {
            oldValue = value;
        }
        //发现value被修改过,就重新读取新的value到OldValue中
        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的值即可.

通过形如上述代码就可以实现一个原子类.不需要使用重量级锁,就可以高效的完成多线程的自增操作. 

实现自旋锁

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

public class SpinLock {
    private Thread owner = null;
    
    public void lock() {
        //通过CAS看当前锁是否被某个线程持有.
        //如果这个锁已经被别的线程持有,那么就自旋等待.
        //如果这个锁没有被别的线程持有,那么就把owner设为当前尝试加锁的线程
        while(!CAS(this.owner, null, Thread.currentThread())) {
            //当owner不为null的时候,循环就会一直执行下去,通过这样的"忙等"来完成等待效果
        }
         
        //阻塞式的等.让线程不参与cpu调度了,此处自旋式的等,没有放弃cpu
        //不会参与到调度,也就没有了调度开锁了.但缺点就是消耗了更多的cpu       
        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是没有影响的.但是不排除一些特殊情况.

假设一个人有1000元存款,他想从中取出500块钱.

取钱的时候ATM卡了,按了一下没反应(t1),又按了一下(t2)还是没反应

按理来说这应该是正常的.

1.存款1000,线程1获取到当前存款值1000,期望更新为500;线程2获取到当前存款值1000,期望更新为500.

2.线程1执行成功.存款被改为500,线程2阻塞等待中.

3.轮到线程2执行了,发现当前存款为500,与之前读到的1000不同,执行失败.

但如果出现了极端情况:比如中间有人给你转了500.

这个时候线程2发现当前存款为1000,与1000相同,又扣款了一次.

这个时候,就被扣款了两次,这都是ABA问题搞的鬼!!

解决方案

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

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

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

  (2)真正修改的时候,

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

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

相关面试题

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

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

2.ABA问题怎么解决

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

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

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

相关文章

基于位的权限系统

基于位的权限系统是一种利用二进制位运算进行权限管理的技术。在这种系统中,不同的权限被编码为2的幂次方 (例如1、2、4、8等),每个权限对应一个独立的二进制位(可想而知运算速度是非常快的)。通过将这些权限值组合在一起形成一个…

day13 滑动窗口最大值 前K个高频元素

题目1:239 滑动窗口最大值 题目链接:239 滑动窗口最大值 题意 长度为K的滑动窗口从整数数组的最左侧移动到最右侧,每次只移动1位,求滑动窗口中的最大值 不能使用优先级队列,如果使用大顶堆,最终要pop的…

React Native集成到现有原生应用

本篇文章以MacOS环境开发iOS平台为例,记录一下在原生APP基础上集成React Native React Native中文网 详细介绍了搭建环境和集成RN的步骤。 环境搭建 必须安装的依赖有:Node、Watchman、Xcode 和 CocoaPods。 安装Homebrew Homebrew是一款Mac OS平台下…

Java SE入门及基础(15)

Java 中的标号(标签 label) 1. 语法规则 标号名称 : 循环结构 2. 作用 标号的作用就是给代码添加一个标记,方便后面使用。通常应用在循环结构中,与break 语句配合使用 3. 应用场景 有如下菜单: 实现其中返回主菜…

C++ | 四、指针、链表

指针 指针用来储存地址定义方式,int *ptr;,使用*来表示所定义的变量是指针取地址符,ptr &a;,通过&来取得一个普通变量的地址,并储存到指针中取值(解引用),想要取得一个指针…

qt.qpa.plugin: Could not find the Qt platform plugin “windows“ in ““

系统环境:Win10家庭中文版 Qt : 5.12.9 链接了一些64位的第三方库,程序编译完运行后出现 qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "" 弹窗如下: 网上搜了一些都是关于pyQt的&#xff0c…

C++刷题 -- 栈和队列

C刷题 – 栈和队列 文章目录 C刷题 -- 栈和队列1.用栈实现队列2.用队列实现栈3.有效的括号4.前 K 个高频元素 1.用栈实现队列 力扣链接 一个栈自然实现不了队列功能,需要使用两个栈一个输入栈,一个输出栈队列是先入先出,当队列push操作&…

外包干了4年,废了···

有一说一,外包没有给很高的薪资,是真不能干呀! 先说一下自己的情况,大专生,19年通过校招进入湖南某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了&#xff0…

专业、高效的解决方案:为企业业务提供强大支持

随着市场竞争的日益激烈,企业要想在众多竞争对手中脱颖而出,必须具备强大的核心竞争力。而这种竞争力的源泉,往往来自于企业所提供的产品和服务。为了确保产品和服务的质量,企业需要不断地进行技术创新和管理创新,以满…

pytest pytest-cov生成代码覆盖率报告

pytest-cov 是一个用于 pytest 的插件,它可以生成代码覆盖率报告。代码覆盖率是一个度量,表示在测试过程中执行了代码的哪些部分。这是一个非常有用的工具,因为它可以帮助你理解你的测试是否全面,是否有遗漏的代码部分。 pytest-c…

性能测试jmeter

选的这些怎么添加 在一个列表里面 方法调用${__time(YMD)} 两个下划线,后跟函数名,小括号内是输入参数,整个用大括号包裹。 注意POST一定要在消息体数据里面写,不能再参数里面 否则报错:loginOut,没cookie等

express服务连接mysql数据库

下载mysql2依赖包 npm i mysql2 创建mysql连接实例并暴露出去 const mysql require(mysql2)const mysqlMode mysql.createPool({host: 127.0.0.1, //服务端hostuser: root, //用户名称,mysql一般默认rootpassword: 123456, //密码database: sqlTest1, //数据库名字…

了解Dubbo配置:优先级、重试和容错机制的秘密【五】

欢迎来到我的博客,代码的世界里,每一行都是一个故事 了解Dubbo配置:优先级、重试和容错机制的秘密【五】 前言Dubbo高级配置概述不同配置覆盖关系重试与容错处理机制负载均衡机制 前言 Dubbo作为一款强大的分布式服务框架,其高级…

【Linux】Git - 新手入门

文章目录 1. git 版本控制器 - 该如何理解?2. git / gitee / github 区别?3. Linux 中 git 的使用3.1 安装 git3.2 使用 github 新建远端仓库3.2.1 账号注册3.2.2 创建代码仓库3.2.3 克隆仓库到本地3.2.4 .gitignore 文件 3.3 使用 git 提交代码到 githu…

【嵌入式学习笔记-02】什么是库文件,静态库的制作和使用,动态库的制作和使用,动态库的动态加载

【嵌入式学习笔记-02】什么是库文件,静态库的制作和使用,动态库的制作和使用,动态库的动态加载 文章目录 什么是库文件?编程模型的发展什么是库文件? 静态库的制作和使用动态库的制作和使用动态库的动态加载 什么是库文…

网络文件共享服务

一.存储类型 直连式存储:Direct-Attached Storage,简称DAS 存储区域网络:Storage Area Network,简称SAN(可以使用空间,管理也是你来管理) 网络附加存储:Network-Attached Storage&…

gogs git创建仓库步骤

目录 引言创建仓库clone 仓库推送代码 引言 Gogs 是一款类似GitHub的开源文件/代码管理系统(基于Git),Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。 创建仓库 git中的组织可以把它看成是相关仓库的集合&#xff0c…

IO网络2.0

思维导图 机械臂 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <myhead.h> #define SER_PORT 8888 #define SER_IP "192.168.122.68" //服务端//TCP客户端 int main(int argc, const char *argv[]) {//创建套接字…

大模型理论基础1

大模型理论基础1 第一章&#xff1a;引言 语言模型 自回归语言模型 概率的链式法则&#xff1a; 在自回归语言模型 p 中生成整个序列 X1:L&#xff0c;我们需要一次生成一个令牌(token)&#xff0c;该令牌基于之前以生成的令牌进行计算获得&#xff1a; 其中T≥0 是一个控…

HNU-算法设计与分析-实验4

算法设计与分析实验4 计科210X 甘晴void 202108010XXX 目录 文章目录 算法设计与分析<br>实验41 回溯算法求解0-1背包问题问题重述想法代码验证算法分析 2 回溯算法实现题5-4运动员最佳配对问题问题重述想法代码验证算法分析 3 分支限界法求解0-1背包问题问题重述想法…