述
在并发编程中,原子类也是经常使用的一个工具,利用原子类,可以把一些操作变成一个原子操作,在多线程的情况下不需要加锁也可以保证线程安全
原子类的作用及优势
原子类的作用跟锁是类似的,都是为了保证在并发环境下的线程安全,原子类相比于锁,有一定的优势
- 锁的粒度更细: 原子类可以把竞争范围缩小到变量级别,通常我们手动加锁的粒度都会大于原子变量的粒度
- 效率更高: 一般情况下,原子类的效率会比使用锁的效率高,除了高度竞争的情况
常用原子类
常用的原子类有以下几种
基本原子类型
Atmoic*
:AtmoicInteger
,AtmoicLong
,AtmoicBoolean
作用: 对基本类型的操作变为原子操作
原子数组类型
Atmoic*Array
:AtmoicIntegerArray
,AtmoicLongArray
,AtmoicReferenceArray
作用: 对数组的操作为原子操作
引用类型原子类
Atmoic*Reference
:AtomicReference
,AtomicStampedReference
,AtomicMarkableReference
作用: 将一个对象转为原子操作
升级类型原子类
Atomic*FieldUpdater
:AtomicIntegerfieldupdater
,AtomicLongFieldUpdater
,AtomicReferenceFieldUpdater
作用: 将一个非原子变量转换为原子变量
累加器
Adder
:LongAdder
,DoubleAdder
Accumulator
: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
要更加灵活
总结
了解常用的原子类,以及一些常用的方法