首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一 . CAS 简介
什么是 CAS ?
CAS操作 —— Compare & Set ,或是 Compare & Swap
CAS 的操作步骤是什么 ? -> 先比较 , 再设置
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。
- CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。
- 当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
CAS 的效率 ?
- CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快
- CAS避免了请求操作系统来裁定锁的问题
CAS 的消耗?
一个8核CPU计算机系统,每个CPU有cache(CPU内部的高速缓存,寄存器),管芯内还带有一个互联模块,使管芯内的两个核可以互相通信
当存在 cache 和 数据不在一个域中时 ?
“最好情况”是指对某一个变量执行 CAS 操作的 CPU 正好是最后一个操作该变量的CPU,所以对应的缓存线已经在 CPU 的高速缓存中了
算法假想
1 | JAVA复制代码do{ |
二 . CAS 的缺陷
问题一 : ABA 问题
- 一个线程 one 从内存位置 V 中取出 A
- 另一个线程 two 也从内存中取出 A ,并且 two 进行了一些操作变成了 B
- two 又将 V 位置的数据变成 A ,这时候线程 one 进行 CAS 操作发现内存中仍然是 A
- one 操作成功。
- 尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。
- 从 Java5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
+ AtomicStampedReference 通过包装 [E,Integer] 的元组,来对对象标记版本戳 stamp
问题二 : 循环时间长开销大
对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
问题三 : 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
三 . CAS 深入分析
在 CAS 中有三个参数:内存值 V、旧的预期值 A、要更新的值 B ,当且仅当内存值 V 的值等于旧的预期值 A 时,才会将内存值V的值修改为 B ,否则什么都不干
主要类 : Unsafe
Unsafe 是 CAS 的核心类 , 他提供了硬件级别得原子操作 (其他情况下Java 需要通过本地 Native 方法访问底层操作系统)
- unsafe.objectFieldOffset
- getAndAddInt -> compareAndSwapInt(Object var1, long var2, int var4, int var5)
- 对以下四个值进行了比较判断 : 对象、对象的地址、预期值、修改值
CPU 的原子操作: CPU 提供了两种方法来实现多处理器的原子操作:总线加锁或者缓存加锁
总线加锁:
总线加锁就是就是使用处理器提供的一个 LOCK# 信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。但是这种处理方式显得有点儿霸道,不厚道,他把CPU和内存之间的通信锁住了,在锁定期间,其他处理器都不能其他内存地址的数据,其开销有点儿大。所以就有了缓存加锁。
缓存加锁:
其实针对于上面那种情况,我们只需要保证在同一时刻,对某个内存地址的操作是原子性的即可。缓存加锁,就是缓存在内存区域的数据如果在加锁期间,当它执行锁操作写回内存时,处理器不再输出LOCK# 信号,而是修改内部的内存地址,利用缓存一致性协议来保证原子性。缓存一致性机制可以保证同一个内存区域的数据仅能被一个处理器修改,也就是说当 CPU1 修改缓存行中的 i 时使用缓存锁定,那么 CPU2 就不能同时缓存了 i 的缓存行。
CAS 主要实现得方式 :
- AtomicInteger
- addAndGet()
四 . CAS CPU 的查询操作
1 | JAVA复制代码 |
五 . CAS 初级原理
1 | java复制代码// Java 里面 CAS 操作主要通过 Native 方法完成 , 主要的操作对象有 : Unsafe |
六 . CAS 深入
1 | java复制代码@ https://blog.csdn.net/qq_37113604/article/details/81582784 |
总结 :
- 程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。
- 如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。
- 反之,如果程序是在单处理器上运行,就省略lock前缀
?- (单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
附录 : Git 源码
本文转载自: 掘金