什么是原子类,有什么作用
- 原子类的作用和锁类似,是为了保证并发的情况下线程安全。不过原子类相比于锁,有一定的优势:
- 粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度。
- 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况。
- 不可分割
- 一个操作时不可中断的,即便是多线程的情况下也可以保证
- java.util.concurrent.atomic
6类原子类纵览
Atomic*基本类型原子类,以AtomicInteger为例
- public final int get() //获取当前的值
- public final int getAndSet(int newValue) //获取当前的值,并设置新的值
- public final int getAndIncrement() //获取当前的值,并自增
- public final int getAndDecrement() //获取当前的值,并自减
- public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
- boolean compareAndSet(int expect, int update) //如果当前面的数值等于预期值,则以原子的方式将该值设置为输入值(update)
Atomic*Array数组类型原子类
数组类型原子类相对于基本类型原子类和数组类型相对于基本类型相同。而数组类型原子类与基本类型原子类用法也相同,只多了一层集合包装。
Atomic*Reference引用类型原子类
AtomicReference:AtomicReference类的作用,和AtomicInteger并没有本质区别,AtomicInteger可以让一个整数保证原子性,而AtomicReference可以让一个对象保证原子性,当然,AtomicReference的功能明显比AtomicInteger强,因为一个对象里面可以包含很多属性。用法和AtomicInteger类似。
把普通变量升级为原子类:用AtomicIntegerFieldUpdater升级原有变量
- AtomicIntegerFieldUpdate对普通变量升级
- 使用场景:偶尔需要一个原子get-set操作
- 注意点:
- 可见范围
原理是反射。 - static修饰的变量
- 可见范围
Adder累加器
- Java 8引入,相对是比较新的一个类
- 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
- 竞争激烈的时候,LongAdder吧不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性
- 对于AtomicLong累加,每一次加法,都要flush和refresh,导致很耗费资源。
- 而LongAdder每个线程会有自己的计数器,仅用来在自己线程内计数,这样一来就不会和其他线程的计数器互相干扰,就不需要一直同步,影响效率。LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数:当竞争不激烈,也就是同步发生的不频繁的时候,直接对base变量进行累加,竞争激烈的时候,个线程分散累加到自己的槽Cell[i]中。
- 在低争用下,AtomicLong和LongAdder这两个类具有相似的特征。但是在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但是要消耗更多的空间;LongAdder适合的场景是统计求和的场景,而且LongAdder基本只提供了add方法,而AtomicLong还具有cas方法
Accumulator累加器
- Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder。
- 需要大量计算并需要并行计算的场景
- 对计算顺序不能有要求
- 与spark的累加器非常相似