CAS以及Atomic原子操作详解
CAS以及Atomic原子操作详解
CAS
什么是CAS
- 针对一个变量,首先比较它在
内存中的值与某个期望的值是否相同
,如果相同就给它赋予新值 - 其原子性是
直接在硬件层面得到保障
的 - CAS是一种
无锁算法
,在不使用锁的情况下实现多线程之间的变量同步
底层
: CAS的底层实现- 从JVM源码层面的看CAS
- 原子性
- 在单核处理器是通过cmpxchgl指令来保证原子性的,在多核处理器下无法保证了,通过lock前缀的加持变为lock cmpxchgl保证了原子性,这里
lock前缀指令拥有保证后续指令的原子性的作用
- 有序性
- 通过
C++关键字volatile禁止指令重排序保证有序性
,对于C++关键字volatile有两个作用一个是禁止重排序,一个是防止代码被优化
- 其中可见性在JVM源码层面是保证的了,因为多核处理器下会加lock前缀指令,但是Java代码层面实现的CAS不能保证get加锁标记和set加锁标记的可见性,比如Atomic类中需要通过volatile修饰state保证可见性
缺陷
: CAS的缺陷- 一般CAS都是配合自旋,
自旋时间过长,可能会导致CPU满载
,所以一般会选择自旋到一定次数去park - 每次
只能保证一个共享变量进行原子操作
- ABA问题
问题
: 什么是ABA问题- 当有多个线程对一个原子类进行操作时,某个线程在这段时间内将A修改到B,又马上将其修改为A
,其他线程并不感知
,还是会被修改成功
问题
: ABA问题的解决方案- 数据库有个锁是乐观锁,是一种通过版本号方式来进行数据同步,也就是
每次更新的时候都会匹配这个版本号,只有符号才能更新成功
,同样的ABA问题也是基于这种去解决的,相应的Java也提供了对应的原子类AtomicStampedRefrence,其内部reference就是我们实际存储的变量,stamp就是版本号,每次修改可以通过加1来保证版本的唯一性
问题
: CAS失败自旋的操作存在什么问题- CAS自旋时间过长不成功,会给CPU带来较大的开销
CAS的应用
CAS操作的是由Unsafe类提供支持,该类定义了三种针对不同类型变量的CAS操作
public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
Atomic原子类
- 在并发编程中很容易出现并发安全的问题,比如自增操作,有可能不能获取正确的值,一般情况想到的是synchronized来保证线程安全,但是由于它是悲观锁,并不是最高效的解决方案,所以Juc提供了乐观锁的方式去提升性能
基本类型
: AtomicInteger、AtomicLong、AtomicBoolean引用类型
: AtomicReference、AtomicStampedRerence、AtomicMarkableReference数组类型
: AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray对象属性原子修改器
: AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater原子类型累加器(JDK8增加的类)
: DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
LongAdder和DoubleAdder
瓶颈详解
- 对于高并发场景下,
多个线程同时进行自旋操作,会出现大量失败并不断自旋的情况
,此时AtomicLong自旋会成为瓶颈,LongAdder引入解决了高并发场景,AtomicInteger、AtomicLong的自旋瓶颈问题
LongAdder原理
- AtomicLong中有个内部变量value保存着实际的值,所有的操作都是针对该变量进行,在高并发场景下,value变量其实就是一个热点,多个线程同时竞争这个热点,而这样冲突的概率就比较大了
重点
: LongAdder的基本思路就是分散热点,将value的值分散到一个数组中,不同线程会命中到这个数组的不同槽位中,各个线程只对自己槽位中的那个值进行CAS操作,这样就分散了热点,冲突的概率就小很多,如果要获取真正的值,只需要将各个槽位的值累加返回- LongAdder设计的精妙之处:
尽量减少热点冲突,不到最后万不得已,尽量将CAS操作延迟
注意
: LongAdder的sum方法会有线程安全的问题- 高并发场景下
除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值
,由于计算总和时没有对Cell数组进行加锁,所以在累加过程中可能有其他线程对于Cell数组中的值因为线程安全无法保障进行了修改,也有可能对数组进行了扩容,所以sum返回的值并不是非常精确的
,其返回值并不是一个调用sum方法的原子快照值
LongAdder逻辑
LongAccumulator
- LongAccumulator是
LongAdder的增强版本
,LongAdder只针对数组值进行加减运算,而LongAccumulator提供了自定义的函数操作 - LongAccumulator
内部原理和LongAdder几乎完全一样
,都是利用了父类Striped64的longAccumulate方法
作者:枫度柚子
链接:https://juejin.cn/post/7101216131397976071
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。