第4章 Java并发包中原子操作类原理剖析
4.1 原子变量操作类
JUC并发包中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作类,它们的原理类似。AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现。
在没有原子类的情况下,实现计数器需要使用一定的同步措施,比如使用synchronized关键字等,但是这些都是阻塞算法,对性能有一定损耗,原子操作类都使用CAS非阻塞算法,性能更好。但是在高并发情况下AtomicLong还会存在性能问题。JDK 8提供了一个在高并发下性能更好的LongAdder类
4.2 JDK 8新增的原子操作类LongAdder
4.2.1 LongAdder简单介绍
AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了,但是JDK开发组并不满足于此。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。
LongAdder在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。
LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。当一开始判断Cell数组是null并且并发线程较少时,所有的累加操作都是对base变量进行的。保持Cell数组的大小为2的N次方,在初始化时Cell数组中的Cell元素个数为2,数组里面的变量实体是Cell类型。Cell类型是AtomicLong的一个改进,用来减少缓存的争用,也就是解决伪共享问题。
LongAdder代码分析
为了解决高并发下多线程对一个变量CAS争夺失败后进行自旋而造成的降低并发性能问题,LongAdder在内部维护多个Cell元素(一个动态的Cell数组)来分担对单个变量进行争夺的开销。下面围绕以下话题从源码角度来分析LongAdder的实现:
- (1)LongAdder的结构是怎样的?
- LongAdder类继承自Striped64类,在Striped64内部维护着三个变量
base
,cellsBusy
,cells
。LongAdder的真实值其实是base的值与Cell数组里面所有Cell元素中的value值的累加,base是个基础值,默认为0。cellsBusy用来实现自旋锁,状态值只有0和1,当创建Cell元素,扩容Cell数组或者初始化Cell数组时,使用CAS操作该变量来保证同时只有一个线程可以进行其中之一的操作。
- LongAdder类继承自Striped64类,在Striped64内部维护着三个变量
- (2)当前线程应该访问Cell数组里面的哪一个Cell元素?
- 在
add()
方法中通过getProbe()& m
进行计算的,其中m是当前cells数组元素个数-1。
- 在
- (3)如何初始化Cell数组?
- 初始化cells数组元素个数为2,然后使用h&1计算当前线程应该访问celll数组的哪个位置,也就是使用当前线程的threadLocalRandomProbe变量值&(cells数组元素个数-1),然后标示cells数组已经被初始化。
- (4)Cell数组如何扩容?
- 将容量扩充为之前的2倍,并复制Cell元素到扩容后数组。另外,扩容后cells数组里面除了包含复制过来的元素外,还包含其他新元素,这些元素的值目前还是null。
- (5)线程访问分配的Cell元素有冲突后如何处理?
- 对CAS失败的线程重新计算当前线程的随机值threadLocalRandomProbe,以减少下次访问cells元素时的冲突机会。
- (6)如何保证线程操作被分配的Cell元素的原子性?
- Cell类内部维护一个被声明为volatile的变量,这里声明为volatile是因为线程操作value变量时没有使用锁,为了保证变量的内存可见性这里将其声明为volatile的。另外该类中的cas函数通过CAS操作,保证了当前线程更新时被分配的Cell元素中value值的原子性。
4.3 LongAccumulator类原理探究
LongAdder类是LongAccumulator的一个特例,只是后者提供了更加强大的功能,可以让用户自定义累加规则。