一文搞懂CAS,CAS原理分析及ABA问题详解
什么是CAS
CAS即Compare And Swap的缩写,翻译成中文就是比较并交换,其作用是让CPU比较内存中某个值是否和预期的值相同,如果相同则将这个值更新为新值,不相同则不做更新,也就是CAS是原子性的操作(读和写两者同时具有原子性),其实现方式是通过借助C/C++调用CPU指令完成的,所以效率很高。
CAS的原理很简单,这里使用一段Java代码来描述
public boolean compareAndSwap(int value, int expect, int update) {
// 如果内存中的值value和期望值expect一样 则将值更新为新值updateif (value == expect) {value = update;return true;} else {return false;}
}
大致过程是将内存中的值、我们的期望值、新值交给CPU进行运算,如果内存中的值和我们的期望值相同则将值更新为新值,否则不做任何操作。这个过程是在CPU中完成的,这里不好描述CPU的工作过程,就拿Java代码来描述了。
Unsafe源码分析
Java是在Unsafe(sun.misc.Unsafe)类实现CAS的操作,而我们知道Java是无法直接访问操作系统底层的API的(原因是Java的跨平台性限制了Java不能和操作系统耦合),所以Java并没有在Unsafe类直接实现CAS的操作,而是通过**JDI(Java Native Interface)**本地调用C/C++语言来实现CAS操作的。
Unsafe有很多个CAS操作的相关方法,这里举例几个
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
我们拿public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);进行分析,这个方法是比较内存中的一个值(整型)和我们的期望值(var4)是否一样,如果一样则将内存中的这个值更新为var5,参数中的var1是值所在的对象,var2是值在对象(var1)中的内存偏移量,参数var1和参数var2是为了定位出值所在内存的地址。
Unsafe.java在这里发挥的作用有:
- 将对象引用、值在对象中的偏移量、期望的值和欲更新的新值传递给Unsafe.cpp
- 如果值更新成功则返回true给开发者,没有更新则返回false
Unsafe.cpp在这里发挥的作用有:
- 接受从Unsafe传递过来的对象引用、偏移量、期望的值和欲更新的新值,根据对象引用和偏移量计算出值的地址,然后将值的地址、期望的值、欲更新的新值传递给CPU
- 如果值更新成功则返回true给Unsafe.java,没有更新则返回false
CPU在这里发挥的作用:
- 接受从Unsafe.cpp传递过来的地址、期望的值和欲更新的新值,执行指令cmpxchg,比较地址中的值是否和期望的值一样,一样则将值更新为新的值,不一样则不做任何操作
- 将操作结果返回给Unsafe.cpp
CAS的缺点
CAS虽然高效的实现了原子性操作,但是也存在一些缺点,主要表现在以下三个方面。
ABA问题
在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
- 线程1,期望值为A,欲更新的值为B
- 线程2,期望值为A,欲更新的值为B
线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。
ABA问题带来的危害:
小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
- 线程1(提款机):获取当前值100,期望更新为50,
- 线程2(提款机):获取当前值100,期望更新为50,
线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
线程3(默认):获取当前值50,期望更新为100,
这时候线程3成功执行,余额变为100,
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。
解决方法:
在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。
循环时间长开销大
如果CAS操作失败,就需要循环进行CAS操作(循环同时将期望值更新为最新的),如果长时间都不成功的话,那么会造成CPU极大的开销。
这种循环也称为自旋
解决方法:
- 限制自旋次数,防止进入死循环。
- 只能保证一个共享变量的原子操作
- CAS的原子操作只能针对一个共享变量。
解决方法:
如果需要对多个共享变量进行操作,可以使用加锁方式(悲观锁)保证原子性,或者可以把多个共享变量合并成一个共享变量进行CAS操作。
CAS的应用
我们知道CAS操作并不会锁住共享变量,也就是一种非阻塞的同步机制,CAS就是乐观锁的实现。
乐观锁
乐观锁总是假设最好的情况,每次去操作数据都认为不会被别的线程修改数据,所以在每次操作数据的时候都不会给数据加锁,即在线程对数据进行操作的时候,别的线程不会阻塞仍然可以对数据进行操作,只有在需要更新数据的时候才会去判断数据是否被别的线程修改过,如果数据被修改过则会拒绝操作并且返回错误信息给用户。
悲观锁
悲观锁总是假设最坏的情况,每次去操作数据时候都认为会被的线程修改数据,所以在每次操作数据的时候都会给数据加锁,让别的线程无法操作这个数据,别的线程会一直阻塞直到获取到这个数据的锁。这样的话就会影响效率,比如当有个线程发生一个很耗时的操作的时候,别的线程只是想获取这个数据的值而已都要等待很久。
Java利用CAS的乐观锁、原子性的特性高效解决了多线程的安全性问题,例如JDK1.8中的集合类ConcurrentHashMap、关键字volatile、ReentrantLock等。
更多关于CAS的原理分析详细讲解可以到:https://www.bilibili.com/video/BV1bg411F7U9/观看
如果你觉得有所收获,可以点赞收藏关注哦,接下来,我将继续对Android的相关知识进行分析和分享,可以继续关注哦,要是真正想学习android开发这块的,或者在这方面工作的朋友我免费分享给你,当然也希望大家都能多多支持我,你们的点赞就是我的动力,谢谢!
一文搞懂CAS,CAS原理分析及ABA问题详解相关推荐
- CAS原理分析及ABA问题详解
什么是CAS? CAS(Compare-and-Swap,比较并替换),CAS需要有3个操作数: 内存地址V,旧的预期值A,新值B; 其作用是当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修 ...
- 【 卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10)】
卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10) 本章主要进行卷积神经网络的相关数学原理和pytorch的对应模块进行推导分析 代码也是通过demo实 ...
- 一文搞懂线程池原理——Executor框架详解
文章目录 1 使用线程池的好处 2 Executor 框架 2.1 Executor 框架结构 2.2 Executor 框架使用示意图 2.3 Executor 框架成员 2.3.1 Executo ...
- 搞懂 SQL 查询优化原理分析,秒速处理大数据量查询
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 有一张财务流水表,未分库分表,目前的数据量为9555695,分页查询使用到 ...
- 一文读懂——全局注意力机制(global attention)详解与代码实现
废话不多说,直接先上全局注意力机制的模型结构图. 如何通过Global Attention获得每个单词的上下文向量,从而获得子句向量呢?如下几步: 代码如下所示: x = Embedding(inpu ...
- 【多输入模型 Multiple-Dimension 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人 (6/10)】
多输入模型 Multiple-Dimension 数学原理分析以及源码源码详解 深度学习 Pytorch笔记 B站刘二大人(6/10) 数学推导 在之前实现的模型普遍都是单输入单输出模型,显然,在现实 ...
- word2vec原理分析与代码公式详解
word2vec 是 Google 于 2013 年开源推出的一个用于获取 word vector 的工具包,原始的论文和网上的很多分析都写的太过粗略,难以理解.现在有根据源码的具体原理分析和详细的公 ...
- 一文读懂汇编程序的多个分段的程序--详解
先贴上程序 首先要知道cpu是不知道到底哪里的数据是代码 那里是数据那里是堆栈因此实现这些要靠程序员来告诉CPU 当然还有小小的汇编程序:汇编中的嵌套循环 assume cs:code,ds:data ...
- 一文搞懂H264量化原理以及计算过程
1.概述 量化是使数据比特率下降的有效工具.量化过程的输入值动态范围很大,需要较多的比特才能表示一个数值,量化后的输出则只需要较小比特表示. 量化是不可逆过程,处理过程中有信息丢失,存在量化误差. H ...
最新文章
- 使用C# 3.0编译器编译 Asp.Net 项目代码
- 机翻降重?掩饰抄袭?SCI期刊上的这些「奇言怪语」,不少来自中国作者
- Hyper-V应用指南之6-差异磁盘的使用[转]
- Android实现手机手电筒
- Android开源项目整理:个性化空间View篇(看遍论坛千万篇,不看此篇也枉然)
- 50个常用元器件图片_盘点2020年wordpress常用的50个插件合集
- lambdas for_Java 8发布了! — Lambdas教程
- unique函数_unique函数使用场景(一)
- mysql有没有实现高可用_MySQL高可用架构:mysql+keepalived实现
- 关于vue中sync修饰符的用法
- Java String 类的方法
- js获取窗口宽度高度
- cf701B Cells Not Under Attack
- cpp的vector初始化方法
- 微信小程序获取openId,传参问题导致参数无效(errcode: 40013、errcode:40125、errcode“:40029)
- 空气净化器的研究概况
- 【《Linus Torvalds自传-Linux OS 之父》摘录】
- Java 核心技术卷 II(第 8 版) – 读书笔记 – 第 1 章(下)
- iPhone开发网站、论坛和博客
- 听歌学日语2 五十音图 たなは行
热门文章
- NGUI制作头顶血条个人小问题解决方法
- Nmap使用NSE进行漏洞扫描
- int类型变量的取值范围
- 基于kubeadm快速部署kubernetes K8S V1.17.4集群-无坑完整版
- *约瑟夫问题。这是十七世纪的法国数学家加斯帕在《数目的游戏问题》中讲的一个故事:15个基督教徒和15个异教徒在海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了个办法:30个人围成一个圆
- unity编辑器上不穿模,真机Android上会穿模问题定位和解决
- python加权求和_Python计算加权平均分的问题?大神求解答。。。。。
- 找工作,面试,这几个坑我希望你们不要踩
- 如何更高效的标注文字检测算法的label(二)
- 主机常见噪音 的原因 及 降噪方法