一、 LongAdder简介
LongAdder类是JDK1.8新增的一个原子性操作类。上一节说到,AtomicLong通过CAS提供了非阻塞的原子性操作,相比用阻塞算法的synchronized来说性能已经得到了很大提升。在高并发下大量线程会同时竞争更新同一个原子变量,但由于只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS操作,这会白白浪费CPU资源。
为了解决AtomicLong在高并发下的缺点,LongAdder应运而生。LongAdder采用的思路是:既然AtomicLong由于过多线程同时去竞争同一个变量的更新而产生性能瓶颈,那么把一个变量分解为多个变量,让同样多的线程去竞争多个资源,就解决了性能问题。
如图4-1:

使用AtomicLong时,是多个线程同时竞争同一个原子变量。
如图4-2:

使用LongAdder时,则是内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取在其他Cell原子变量上进行CAS尝试,这增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base值返回的。
LongAdder维护了一个Cells数组和一个基值变量base,Cells数组的特点如下:
- 延迟初始化(默认情况下Cells为null)。因为Cells占用内存相对较大,故惰性加载。Cells为null时且并发线程较少时,所有的累加操作都是对base变量进行的。
- 初始化时,Cells数组中Cell元素个数为2;同时,Cell数组中元素个数保存为2的N次方。
Cell 类是AtomicLong的一个改进,用来减少缓存的争用,即解决伪共享问题。对于大多数孤立的多个原子操作进行字节填充是浪费的,因为原子操作都是无规律地分散在内存中进行的,多个原子变量被放入同一个缓存行的可能性很小。但是原子性数组元素的内存地址是连续的,故数组内的多个元素能经常共享缓存行(伪共享),因此Cell类使用了@sun.misc.Contended注解进行字节填充,这是为了防止数组中多个元素共享一个缓存行,从而提升性能。
二、LongAdder代码分析
为了解决高并发下多线程对一个变量 CAS 争夺失败后进行无限自旋而造成的降低并发性能的问题,LongAdder在内部维护了一个动态的Cell数组来分担对单个变量进行争夺的开销。
这一节我们来围绕以下话题对LongAdder的实现进行分析:
- (1)LongAdder的结构
- (2)当前线程应该访问Cells数组里的哪一个Cell元素
- (3)如何初始化Cells数组
- (4)Cells数组的扩容机制
- (5)线程访问所分配的Cell元素有冲突后如何处理
- (6)如何保证线程操作被分配的Cell元素的原子性
(1)LongAdder的结构
如图,LongAdder类继承自Striped64类:
LongAdder原理剖析

本文深入剖析了JDK1.8中的LongAdder类的工作原理,包括其内部结构、线程分配Cell机制、Cells数组初始化及扩容过程等,并探讨了如何解决线程间访问Cell元素的冲突。
最低0.47元/天 解锁文章
636

被折叠的 条评论
为什么被折叠?



