搞懂多线程(八)之原子操作类

一、概述

二、基本类型原子类

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、总结

  1. 以一种线程安全的方式操作非线程安全对象内的某些字段

  2. 更新的对象属性必须使用public/private volatile修饰符

  3. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法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]),这样热点就被分散了,冲突的概率就小很多。

  1. 如果Cells表为空,尝试用CAS更新base字段,成功则退出

  2. 如果Cells表为空,CAS更新base字段失败,出现竞争,uncontendedtrue,调用longAccumulate

  3. 如果Cells表非空,但当前线程映射的槽为空uncontendedtrue,调用longAccumulate

  4. 如果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数组中的valuebase累加作为返回值,核心的思想就是将之前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操作