什么是原子类?
就是可以保证线程安全的原子操作的数据类型。
有什么作用?
原子类的作用和锁类似,为了保证并发情况下的线程安全。原子类和锁相比,要比锁的粒度更细,效率更高。
下面我们就逐个介绍以上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*Reference引用类型原子类
- AtomicReference:AtomicReference类的作用,和AtomicInteger并没有本质区别,AtomicTnteger
- 可以让一个整数保证原子性,儿AtomicReference可以让一个对象保证原子性,当然,AtomicReference的功能明显比AtomicTnteger强,因为一个对象里可以包含很多属性。用法和AtomicTnteger类似。
AtomicIntegerFieldUpdater对普通变量进行升级
- AtomicIntegerFieldUpdater:把普通变量升级为具有原子功能
- 使用场景:偶尔需要一个原子get-set操作
- 注意点:
1.可见范围:变量如果是private修饰,则无法升级
2.不支持static
Adder累加器
- 累加器是java 8引入的,相对是比较新的一个类
- 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
- 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性。
上图为AtomicLong的数据在不同线程中获取的过程:
当一个AtomicLong的数据变化时,会先把数据从本地缓存区flush到共享缓存区,然后别的线程读取的时候,会先从共享缓存区refresh到本地缓存区,这样才能获取到AtomicLong的最新数据。
- 很明显,这种操作虽然保证了数据的安全性,但是在高并发情况下,大量的flush、refresh操作会严重影响效率。而Adder累加器则是在此基础上进行了优化改进。
如图中所示,第一个线程的计数器数值,也就是ctr为1的时候,可能线程2的计数器ctr的数值已经是3了,他们之间不存在竞争关系,所以在加和的过程中,根本不需要同步机制,也不需要刚才的flush和refresh操作。这里也没有一个公共的counter来给线程统一计数。改进和原理
- LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数。
- base变量:竞争不激烈,直接累加到该变量上
- Cell[]数组:竞争激烈,各个线程分散累加到自己的槽Cell[i]中
上图为LongAdder的sum方法源码,我们可以看到,sum方法是没有加锁的,因此在求和的时候如果Cell数组被修改,则可能会出现数值不准确的情况。
对比AtomicLong和LongAdder
- 在低争用下,在AtomicLong和LongAdder这两个类具有相似的特征。但是在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但要消耗更多的空间
- LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add方法,儿AtomicLong还具有cas方法。
Accumulator累加器
Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder
使用场景:大量数字计算,并且高并发场景下