述
在并发编程中,原子类也是经常使用的一个工具,利用原子类,可以把一些操作变成一个原子操作,在多线程的情况下不需要加锁也可以保证线程安全
原子类的作用及优势
原子类的作用跟锁是类似的,都是为了保证在并发环境下的线程安全,原子类相比于锁,有一定的优势
- 锁的粒度更细: 原子类可以把竞争范围缩小到变量级别,通常我们手动加锁的粒度都会大于原子变量的粒度
- 效率更高: 一般情况下,原子类的效率会比使用锁的效率高,除了高度竞争的情况
常用原子类
常用的原子类有以下几种
基本原子类型
Atmoic*:AtmoicInteger,AtmoicLong,AtmoicBoolean
作用: 对基本类型的操作变为原子操作
原子数组类型
Atmoic*Array:AtmoicIntegerArray,AtmoicLongArray,AtmoicReferenceArray
作用: 对数组的操作为原子操作
引用类型原子类
Atmoic*Reference:AtomicReference,AtomicStampedReference,AtomicMarkableReference
作用: 将一个对象转为原子操作
升级类型原子类
Atomic*FieldUpdater:AtomicIntegerfieldupdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater
作用: 将一个非原子变量转换为原子变量
累加器
Adder:LongAdder,DoubleAdderAccumulator:LongAccumulator,DoubleAccumulator
作用: 高效累加操作
原子基本类型使用
以 AtmoicInteger 为例,了解一下基本的用法,以及常用的方法
1 | public class AtomicIntegerTest { |
原子数组这里就不再介绍了
引用原子类型介绍
AtomicReference 这个类的作用,和 AtmoicInteger 本质上没有却别, AtmoicInteger 是让一个整形变量保证原子性, AtomicReference 可以让一个对象保证原子性
在之前锁的章节,有一个自旋锁的案例,已经用到这个类型了, 代码再贴过来回顾一下
1 | public class SpinLock { |
这里就用到了一个原子类的 compareAndSet 这个CAS操作,对比并赋值
升级类型原子类的使用
升级类型原子类,功能是把一个普通的变量升级为原子类,主要的使用场景是当一个普通变量偶尔需要原子操作的时候使用
以 AtomicIntegerfieldupdater 为例,就是将一个整形升级为原子整形
1 | public class AtomicIntegerFieldUpdaterTest implements Runnable{ |
这里就会把 testCandidate 这个对象的 score 变为原子操作
Adder累加器
在高并发下 LongAdder 的效率比 AtomicLong 要高
使用方式就是 new LongAdder(); 然后调用 add() 方法就可以了
那么为什么 AtomicLong 的性能不如 LongAdder 呢?
AtomicLong 每次执行修改操作,都要做一次 flush 和 refresh 操作,在高并发的情况下效率就很低了,而 LongAdder 每个线程都会有一个自己的计数器,用来在自己的线程内计算,这样就不会被其他线程干扰,等最后要获取值的时候,把所有线程的和在加起来就是总和
对比 AtomicLong
在低争用下, AtomicLong 和 LongAdder 这两个类具有相似的特征,但是在竞争激烈的情况下,LongAdder 的预期吞吐量要高得多,但要消耗更多的空间
LongAdder 适合的场景是统计求和计数的场景,而且 LongAdder 基本只提供了 add 方法,而 AtomicLong 还具有cas方法
Accumulator 累加器
Accumulator 是一个更加灵活的累加器,使用方式上跟上面的 LongAdder 是有些不同的,如下1
2
3
4
5
6
7LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
LongAccumulator accumulator1 = new LongAccumulator((x, y) -> x + y, 0);
accumulator.accumulate(1);
// 获取后清空
accumulator.getThenReset();
代码中的 accumulator 和 accumulator1 作用都是一样的, 就是lambda的两种写法,下面这个看着更直观一点
LongAccumulator 需要两个参数,第一个是 LongBinaryOperator 的实现他是一个函数式接口, 然后传参的时候传具体的实现就行了,第二个参数 0 就是 x 的初始值
上面代码中,刚初始化的时候 x 就是 0 ,然后第一次累加的时候会把x的值给y,然后再把传进来的x跟y做累加,结果赋值给 y
当然这个表达式不一定就是累加,也可以做自己的实现,所以这个比上面的 LongAdder 要更加灵活
总结
了解常用的原子类,以及一些常用的方法