搞懂多线程(八)之原子操作类
一、概述
二、基本类型原子类
1、AtomicInteger
概念
可以原子方式更新的int
值
构造方法
常用方法
2、AtomicBoolean
与AtomicInteger
类似,略
3、AtomicLong
与AtomicInteger
类似,略
底层使用CAS思想,为确保原子性,底层使用do...while循环,在低更新争用下,可以使用。
三、数组类型原子类
1、AtomicIntegerArray
概念
一个int
数组,其中元素可以原子方式更新
构造方法
常用方法
与数组
基本类似,略
2、AtomicLongArray
与AtomicIntegerArray
类似,略
3、AtomicReferenceArray
与AtomicIntegerArray
类似,略
四、引用类型原子类
1、AtomicReference<T>
概念
可以原子方式更新的对象引用,泛型为自定义的引用数据类型的类
2、AtomicStampedReference<T>
概念
携带版本号的原子引用类,可以记录修改次数
3、AtomicMarkableReference<T>
概念
携带标记的原子引用类,只能记录是否被修改
五、对象的属性修改原子类
1、AtomicIntegerFieldUpdater
原子更新对象中int类型字段的值
2、AtomicLongFieldUpdater
原子更新对象中Long类型字段的值
3、AtomicRefere nceFieldUpdater
原子更新引用类型字段的值
4、总结
以一种线程安全的方式操作非线程安全对象内的某些字段
更新的对象属性必须使用public/private volatile修饰符
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法
newUpdater()
创建一个更新器,并且需要设置想要更新的类和属性
五、原子操作增强类
1、LongAdder
概念
一个或多个变量,它们共同维持最初的零long
总和
初始值从0
开始,加减的步长
只能为1
底层原理
Cell[]数组:高并发,累加进各个线程自己的槽Cell[i]中
base变量:低并发,直接累加到该变量上
以LongAdder.add(long x)举例
LongAdder的基本思路就是分散热点
,首先将值存入base
中,然后调用unsafe.compareAndSwapLong()
方法,随着压力增大,该方法返回false
,则初始化一个的Cell[2]
数组,将value值分散到一个Cell数组
中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS
操作,随着压力继续增加则会再次初始化2倍Cell数组
(new Cell[之前数组长度<<1]),这样热点就被分散了,冲突的概率就小很多。
如果
Cells表为空
,尝试用CAS
更新base字段
,成功则退出如果
Cells表为空
,CAS更新base字段失败
,出现竞争,uncontended
为true
,调用longAccumulate
如果
Cells表非空
,但当前线程映射的槽为空
,uncontended
为true
,调用longAccumulate
如果
Cells表非空
,且前线程映射的槽非空,CAS更新Cell
的值,成功则返回,否则uncontended
设为false
,调用longAccumulate
longAccumulate()详解
上述代码首先给当前线程分配一个hash值
,然后进入一个for(;;)
自旋,这个自旋分为三个分支
CASE1:
Cell[]
数组已经初始化CASE2:
Cell[]
数组未初始化(首次新建)CASE3:
Cell[]
数组正在初始化中
CASE2: Cell[]数组未初始化(首次新建)
如果上面条件都执行成功就会执行数组的初始化及赋值操作, Cell[]rs =new Cell[2]
表示数组的长度为2
rs[h &1]= new Cell(x)
表示创建一个新的Cell元素
value是x值,默认为1,h&1
类似于我们之前HashMap
常用到的计算散列桶index
的算法通常都是hash & (table.len- 1)
。同hashmap一个意思。
CASE3: Cell[]数组正在初始化中
该分支实现直接操作base基数,将值累加到base上,也即其它线程正在初始化,多个线程正在更新base的值。
CASE1: Cell[]数组已经初始化
LongAdder.sum():不是强一致性的,它是最终一致性的
sum()
会将所有Cell数组中的value
和base
累加作为返回值,核心的思想就是将之前AtomicLong
一个value的更新压力分散到多个value中去,从而降级更新热点。
LongAdder.sum()结果可能不准确:
首先,最终返回的sum局部变量,初始被复制为base,而最终返回时,很可能base己经被更新了,而此时局部变量sum不会更新,造成不一致。
其次,这里对cell的读取也无法保证是最后一次写入的值。
所以, sum方法在没有并发的情况下,可以获得正确的结果。
构造方法
常用方法
2、LongAccumulator
概念
使用提供的函数一起维护运行的long
值的一个或多个变量
构造方法
LongBinaryOperator:自定义的计算方法,
identity:计算初始值
常用方法
4、DoubleAdder
与LongAdder
类似,略
3、DoubleAccumulator
与LongAccumulator
类似,略
七、AtomicLong与LongAdder对比
AtomicLong:
线程安全,可允许一些性能损耗
要求高精度时可使用保证精度,性能代价
是多个线程下针对单个热点值value进行原子操作
LongAdder:
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
保证性能,精度代价
是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作