CAS
操作的弊端和规避措施
1. CAS 操作的弊端
CAS
操作的弊端主要有以下
4
点。
(1)ABA问题
使用
CAS
操作内存数据时,当数据发生过变化也能更新成功,如操作序列
A==>B==>A
时,最
后一个
CAS
的预期数据
A
实际已经发生过更改,但也能更新成功,这就产生了
ABA
问题。
ABA
问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候将版
本号加
1
,那么操作序列
A==>B==>A
的就会变成
A1==>B2==>A3
,如果将
A1
当作
A3
的预期数据,
就会操作失败。
JDK
提供了两个类
AtomicStampedReference
和
AtomicMarkableReference
来解决
ABA
问题。比较
常用的是
AtomicStampedReference
类,该类的
compareAndSet
方法的作用是首先检查当前引用是否等
于预期引用,以及当前印戳是否等于预期印戳,如果全部相等,就以原子方式将引用和印戳的值一
同设置为新的值。
(2)只能保证一个共享变量之间的原子性操作
当对一个共享变量执行操作时,我们可以使用循环
CAS
的方式来保证原子操作,但是对多个
共享变量操作时,
CAS
无法保证操作的原子性。
一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。
JDK
提供了
AtomicReference
类来保证引用对象之间的原子性,可以把多个变量放在一个
AtomicReference
实例后再进行
CAS
操作。比如有两个共享变量
i
=
1
、
j=2
,可以将二者合并成一个
对象,然后用
CAS
来操作该合并对象的
AtomicReference
引用。
(3)无效CAS会带来开销问题
自旋
CAS
如果长时间不成功(不成功就一直循环执行,直到成功为止),就会给
CPU
带来非
常大的执行开销。
(4)在部分CPU平台上存在“总线风暴”问题
CAS
操作和
volatile
一样也需要
CPU
进行通过
MESI
协议各个内核的“
Cache
一致性”,会通过
CPU
的
BUS
(总线)发送大量
MESI
协议相关的消息,产生“
Cache
一致性流量”。因为总线被设计
为固定的“通信能力”,如果
Cache
一致性流量过大,总线将成为瓶颈,这就是所谓的“总线风暴”。
2. 提升 CAS 性能
提升
CAS
性能有效方式之一是以空间换时间,分散竞争热点。较为常见的方案为:
1
)分散操作热点,使用
LongAdder
替代基础原子类
AtomicLong
,
LongAdder
将单个
CAS
热点
(
value
值)分散到一个
cells
数组中。
2
)使用队列削峰,将发生
CAS
争用的线程加入一个队列中排队,降低
CAS
争用的激烈程度。
JUC
中非常重要的基础类
AQS
(抽象队列同步器)就是这么做的。
提升
CAS
性能有效方式之二是使用线程本地变量,从根本上避免竞争。
声明:本文来源于网络