什么是CAS?CAS有什么缺点?
文章目录
- 什么是CAS
- CAS 举例说明
- CAS 底层实现
- CAS缺陷
什么是CAS
CAS 的全称是 Compare And Swap 即比较交换,其算法核心思想如下函数:CAS(V,E,N) 参数:
- V 表示要更新的变量
- E 预期值
- N 新值
如果 V 值等于 E 值,则将 V 的值设为 N。若 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。
通俗的理解就是 CAS 操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行 CAS 操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。
我们看一下例子:
- 在内存地址V当中,存储这值为10的变量
2. 此时线程1想要把变量的值增加1。对于线程1来说,旧的预期值为E=10,要修改的值 N=11。
3. 线程1要提交更新之前,另一个线程2抢先一步,把内存地址V的变量值先更改成了11
4. 线程1开始提交更新,首先进行E和内存V中实际值比较,发现E不等于V的实际值,提交失败。
5. 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值,此时对线程1来说,E=11,N=12。这个重新尝试的过程被称为自旋。
6. 这一次没有发现其他线程改变地址V的值。线程1进行Compare ,发现N和地址V的实际值是相等的。
7.线程1进行swap,把地址V的值替换为N ,也就是12.
CAS 举例说明
public static int count = 0;public static void main(String[] args) {//for (int i=0;i<5;i++){new Thread(new Runnable() {@Overridepublic void run() {try{Thread.sleep(1000);}catch (Exception e){}//每个线程当中让count值自增100次for(int j=0;j< 10000;j++){count++;}}}).start();}try{Thread.sleep(2000);}catch (Exception e){}System.out.println("count=="+count);
}
如上示例程序,因为上面代码不是线程安全的,所以最终的结果可能会小于 50000。
那么怎么解决呢?可以加锁(synchronized)。
public static int count = 0;public static void main(String[] args) {//for (int i=0;i<5;i++){new Thread(new Runnable() {@Overridepublic void run() {try{Thread.sleep(1000);}catch (Exception e){}//每个线程当中让count值自增100次for(int j=0;j< 10000;j++){synchronized (Demo1.class) {count++;}}}}).start();}try{Thread.sleep(2000);}catch (Exception e){}System.out.println("count=="+count);
}
加了同步锁以后,count自增的操作变成了原子性操作,所以最终的输出一定是count = 50000。
Synchronized 的确保证了线程安全,但是在某些情况下,却不是一个最优选择。
为什么这么说?关键在于性能问题
Synchronized 关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLED状态,这个过程中设计到的操作系统用户模式和内核模式,代价比较高。
尽管jdk1.6 为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低
java原子操作类,指的是java.util.concurrent.atomic包下,一些列以Atomic开头的包装类。例如AtomicBoolean、AtomicInteger、AtomicLong。他们分别用于boolean、Integer、Long类型的原子性操作。
现在我们尝试在代码中引入**AtomicInteger **类:
public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) {//for (int i=0;i<5;i++){new Thread(new Runnable() {@Overridepublic void run() {try{Thread.sleep(1000);}catch (Exception e){}//每个线程当中让count值自增100次for(int j=0;j< 10000;j++){synchronized (Demo1.class) {count.incrementAndGet();}}}}).start();}try{Thread.sleep(2000);}catch (Exception e){}System.out.println("count=="+count.get());
}
使用AtomicInteger之后,最终的输出结果同样可以保证是50000。并且在某些情况下,代码的性能会比Synchronized更好。
Atomic 操作类的底层,正是利用了CAS机制;
CAS 底层实现
下面看一下AtomicInteger的源代码
public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}
}private volatile int value;
public final int get() {return value;
}
这段代码是一个无限循环,也就是CAS的自旋。循环体当中做了三件事:
- 获取当前值
- 当前值+1,计算出目标值
- 进行CAS操作,如果成功则跳出循环,如果失败则重复上述步骤
这里需要注意的重点是get方法,这个方法的作用是获取变量的当前值。
如何保证获得的当前值时内存中的最新值呢?很简单,用volatile关键字来保证,
可是compareAndSet方法是如何保证原子性操作的呢??
接下来看一看compareAndSet方法的实现,以及方法所依赖对象的来历:
compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。
**什么是unsafe呢?**Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。
至于valueOffset对象,是通过unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单地把valueOffset理解为value变量的内存地址。
而unsafe的compareAndSwapInt方法参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。
正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。
CAS缺陷
1.ABA 问题
因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。
如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。
2.循环时间长 开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
2.只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子
性,这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作
什么是CAS?CAS有什么缺点?相关推荐
- 什么是CAS?CAS的作用以及缺点
老顾聊技术 2019-06-03 00:28:00 欢迎关注头条号:老顾聊技术 精品原创技术分享,知识的组装工 前言 这道题是考察面试者的并发编程的知识,关于悲观锁和乐观锁的. 锁 回答这个问题,可以 ...
- linux系统怎么安装cas,CAS 在Linux中安装与配置
一.首先将 /root/cas/ 下的 page文件夹删除 只留以下两个 二.创建cas 文件夹 并将root 下的tomcat压缩包 和 cas.war 复制进去 [root@hostname ~ ...
- php 模拟 cas,CAS的PHP客户端实践:PHP程序实现单点登录
loading... 兄弟近日尝试将一个php程序以单点登录方式和原有的系统整合在一起. 验证服务器选用的是CAS,其提供有相应的php客户端. 整个过程如下: 1.搭建CAS服务器 2.搭建php应 ...
- C语言链表无锁化CAS,CAS无锁操作
主要讲的是<Implementing Lock-Free Queues>的论点,具体直接看论文最好.这里总结些要点. CAS就是Compare And Swap.gcc可以调用: __sy ...
- CAS——cas server环境搭建
环境要求 JDK 8+ CAS 5.2 tomcat 8+ cas server 客户端模板下载 https://github.com/apereo/cas-overlay-template 修改ho ...
- Java多线程学习三十九:CAS 有什么缺点?
CAS 有哪几个主要的缺点. 首先,CAS 最大的缺点就是 ABA 问题. 决定 CAS 是否进行 swap 的判断标准是"当前的值和预期的值是否一致",如果一致,就认为在此期间这 ...
- 从底层吃透java内存模型(JMM)、volatile、CAS
前言 随着计算机的飞速发展,cpu从单核到四核,八核.在2020年中国网民数预计将达到11亿人.这些数据都意味着,作为一名java程序员,必须要掌握多线程开发,谈及多线程,绕不开的是对JMM(Java ...
- Java中的锁原理、锁优化、CAS、AQS详解
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:景小财 www.jianshu.com/p/e674ee68 ...
- 面试官:说说 Java 中的 Unsafe 和 CAS
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://urlify.cn/nAVjM ...
最新文章
- rsyslog的学习
- intent和手势探测
- 如何在Amazon AWS上设置一台Linux服务器
- VTK:PolyData之PointLocatorRadius
- sed之G、H、g、h使用
- 合肥注册公司(各区注册地点说明)
- ActiveMQ 认证
- Atitit 法学体系树与知识点attilax大总结 法学体系		 0301法学类	030101 法学理论	宪法 行政法 民法 商法 婚姻法和继承法 经济法 社会法 刑法 民事诉讼法 行政诉讼法
- JavaScript库和框架
- 自己设计个动态屏保吧
- 用命令修改oracle的密码,用命令修改Oracle数据库密码
- 树的计数 + prufer序列与Cayley公式(转载)
- Oracle EBS流程之--PO ER Model
- 深入剖析RGB、CMYK、HSB、LAB
- python计算加权平均分_python – 使用pandas数据帧计算加权平均值
- 求学信计算机专业英语,求学信模板英文
- 电影光盘的vob格式视频如何转换成mp4格式
- MyExcel 3.9.8 版本发布
- leetcode(17~30)
- 范美忠妻子:美忠是个好男人
热门文章
- 轮毂电机主动减振系统及其垂向性能优化
- 鸿蒙系统安卓模拟器,华为鸿蒙OS再次爆出缺陷!被误判为安卓模拟器:不属于手机系统设备...
- 博客质量分计算(一)
- arcgis生成剖面图(利用3D Analyst 工具上的插入线工具 )
- 《C语言程序设计》课程设计 -- 火车票票务管理系统
- 等待半世的婚礼——林汐
- Visual C++6.0的安装和使用
- 【文献阅读】ShEF: Shielded Enclaves for Cloud FPGAs
- ubuntu串口计数
- css vertical-align属性详解