1.什么是CAS
在并发的时候,我认为V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那我就不修改了,避免多人同时修改导致出错。CAS
有三个操作数:内存值∨、预期值A、要修改的值B,当且仅当预期值A和内存值∨相同时,才将内存值修改为B,否则什么都不做。最后返回现在的V值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Zma6ucB-1636097856684)(https://i.loli.net/2021/10/28/GvlIsXypZ8ThR4n.png)]
2.应用场景
- 乐观锁
- 并发容器
- 原子类
3.以 AtomicInteger
为例,分析在Java
中是如何利用CAS
实现原子操作的?
AtomicInteger
加载Unsafe
工具,用来直接操作内存数据- 用
Unsafe
来实现底层操作,Unsafe
是CAS
的核心类。Java
无法直接访问底层操作系统,而是通过本地(native
)方法来访问。不过尽管如此,JVM
还是开了一个后门,JDK
中有一个类Unsafe
,它提供了硬件级别的原子操作,valueoffset
表示的是变量值在内存中的偏移地址,因为Unsafe
就是根据内存偏移地址获取数据的原值的,这样我们就能通过unsafe
来实现CAS
了,Unsafe
类中compareAndSwapInt
方法,方法中先想办法拿到变量value
在內存中的地址,通过Atomic: cmpxchg(x,address,e)
实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值。至此,最终完成了CAS
的全过程。 - 用
volatile
修饰value
字段,保证可见性
4.缺点
ABA
问题
CAS
需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS
进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA
问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A
就会变成1A-2B-3A
自旋时间过长
自旋CAS
如果长时间不成功,会给CPU
带来非常大的执行开销。如果JVM
能支持处理器提供的pause
指令那么效率会有一定的提升,pause
指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline
),使CPU
不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation
)而引起CPU
流水线被清空(CPU pipeline flush
),从而提高CPU的执行效率。