发布日期:
2024-06-13
文章字数:
935
阅读时长:
3 分
阅读次数:
- CAS(Compare And Swap,比较并交换):针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值
- 一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的
- CAS缺陷
- 自旋 CAS 长时间地不成功,则会给 CPU 带来非常大的开销
- 只能保证一个共享变量原子操作
- ABA 问题
- 加时间戳的原子操作类(AtomicStampedReference<V>)
- AtomicMarkableReference(只关心是否修改过,而不关心修改次数)
- java.util.concurrent.atomic
- 基本类型:AtomicInteger、AtomicLong、AtomicBoolean
- 引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference
- 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 对象属性原子修改器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 字段必须是volatile类型的
- 调用者能够直接操作对象字段(修饰符public/protected/default/private),但是子类是不能操作父类的字段,尽管子类可以访问父类的字段
- 只能是实例变量,不能是类变量,也就是说不能加static关键字
- 只能是可修改变量,不能使final变量,因为final的语义就是不可修改(实际上volatile和final的语义本来就是冲突的)
- 不能修改其包装类型,如果要修改包装类型就需要使用AtomicReferenceFieldUpdater
- 原子类型累加器(jdk1.8增加的类):DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
- LongAdder解决高并发环境下AtomicLong的自旋瓶颈问题:尽量减少热点冲突,不到最后万不得已,尽量将CAS操作延迟
- 基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作
- 如果要获取真正的long值,只要将各个槽中的变量值累加返回
- LongAdder的内部结构:一个base变量,一个Cell[]数组
- base变量:非竞态条件下,直接累加到该变量上
- Cell[]数组:竞态条件下,累加到各个线程自己的槽Cell[i]中
- LongAdder#add
- CPU核数,用来决定槽数组的大小,大小为2的次幂
- 没有遇到并发竞争时,直接使用base累加数值
- 只有从未出现过并发冲突的时候,base基数才会使用到,一旦出现了并发冲突,之后所有的操作都只针对Cell[]数组中的单元Cell
- 初始化cells数组时,必须要保证cells数组只能被初始化一次(即只有一个线程能对cells初始化),他竞争失败的线程会讲数值累加到base上
- 如果Cell[]数组未初始化,会调用父类的longAccumelate去初始化Cell[],如果Cell[]已经初始化但是冲突发生在Cell单元内,则也调用父类的longAccumelate,此时可能就需要对Cell[]扩容了
- 累加到各个线程自己的槽Cell[i]中
- Striped64#longAccumulate
- LongAdder#sum:返回累加的和,也就是"当前时刻"的计数值(计算总和时没有对Cell数组进行加锁)
- 高并发时,除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值
- 此返回值可能不是绝对准确的,因为调用这个方法时还有其他线程可能正在进行计数累加,方法的返回时刻和调用时刻不是同一个点,在有并发的情况下,这个值只是近似准确的计数值
- LongAccumulator是LongAdder的增强版:提供了自定义的函数操作(LongBinaryOperator)
- 内部原理和LongAdder几乎完全一样,都是利用了父类Striped64的longAccumulate方法