05
2017
10

轻而快的CAS

面试官: 讲讲CAS,CAS会有什么问题?

慢腾腾的synchronized

并发世界,分秒必争,快者为王,synchronized那厮,整天情绪消极,完全一个悲观主义者,怎么说呢?

每当他访问一个共享变量的时候,他都认为这个共享变量会被其他人所修改,所以他就给这个共享变量加一个锁,自己独占着,这样别人就不能修改了

可是这样导致一个结果就是每当一个线程要来修改这块共享变量的时候,都会被挂起,这样一来,操作系统老大就要为这个线程保存一些上下文了(一些线程执行时所需要的信息),这个过程很耗时,所以别人见了synchronized都叫他慢老头(Java SE 1.6之前的synchronized)

乐观的CAS

相比synchronized而言,CAS比他乐观多了,他们两个可以说是两个世界的人,CAS每次修改共享变量的时候,都认为没人修改它,自己乐呵呵的就去修改了

在并发的情况下,这不是很危险吗,如果有多个线程都去修改这个共享变量怎么办?

CAS当然考虑这种情况了,不然的话,在J.U.C(java.util.concurrent)中使用的频率怎么会那么高

在CAS修改一个共享变量的时候,他会先检查正在修改的这个变量的值和预期的值是否一样,如果一样,才会修改,不一样的话就重试或者采取其他策略

CAS的这种先判断,再修改,有效的解决了多线程竞争的问题

现在正式介绍一下CAS,他是一个原子操作,他有三个操作数,分别是变量地址V预期值A新值B,当且仅当预期值A等于变量地址V处变量的值时,才会把新值B赋给V处的变量

不妨看一个图:
这里写图片描述

CAS深受大家的喜爱,因为他不用将别的线程挂起,从而引起大的时间开销,并且在大多数情况下,线程之间也不会竞争,CAS成功替换的几率还是很大的

CAS的美中不足

人无完人,谁都有缺点,CAS也不例外,有三大不足一直困扰这CAS

① ABA问题

假设一种情景,进行CAS的线程提取某一个内存的值为A作为预期值,过一段时间后,他会检查这个内存值是否还是A,但是在这一段时间内,如果另一个线程修改该内存的值A为B,然后又将B修改为A

那么当进行CAS的线程进行判断的时候,会认为这个内存的值没有变,但实际上是变了。具体哪种场景会出问题,请参考此博文

那么该如何解决该问题呢?

有一个比较好的思路,就是给变量前再加一个版本号,在每次修改变量的时候版本号加 1,这样 A -> B ->A,就会变成 1A->2B->3A,这样一来就可以识别是不是之前的A了

② 循环时间长

如果CAS不成功,一般会再次的进行重试(重新CAS操作),那么这就导致一个问题,就是有可能长时间都不成功

③ 只能保证一个共享变量的原子操作

当CAS修改一个共享变量的时候,可谓风头出尽,可是当需要对多个变量进行原子操作的时候,CAS有点没底气了,只见synchronized老头在一旁幸灾乐祸,说了句:“加锁啊”

CAS发话了,不加锁也可以实现啊,你看,如果有两个变量i = 2,j = a,我合并一下 ij = 2a,这就可以CAS了啊,再说了,JDK老大1.5后对外提供了AtomicReference类来保证引用对象之间的原子性呢,CAS怼了回去。


本文参考《Java并发编程艺术》 方腾飞等
http://www.cnblogs.com/549294286/p/3766717.html
https://www.ibm.com/developerworks/cn/java/j-jtp11234/index.html

上一篇:将项目上传到github 下一篇:安卓开发-intent属性总结