LongAdder源码学习

学习视频参考

在 JDK1.8 中,Java 提供了一个新的原子类 LongAdder。LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好,代价就是会消耗更多的内存空间。

LongAdder 的原理就是降低操作共享变量的并发数,也就是将对单一共享变量的操作压力分散到多个变量值上,将竞争的每个写线程的 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的 value 值进行 CAS 操作,最后在读取值的时候会将原子操作的共享变量与各个分散在数组的 value 值相加,返回一个近似准确的数值。

LongAdder 内部由一个 base 变量和一个 cell[] 数组组成。当只有一个写线程,没有竞争的情况下,LongAdder 会直接使用 base 变量作为原子操作变量,通过 CAS 操作修改变量;当有多个写线程竞争的情况下,除了占用 base 变量的一个写线程之外,其它各个线程会将修改的变量写入到自己的槽 cell[] 数组中,最终结果可通过以下公式计算得出:

img

LongAdder 在操作后的返回值只是一个近似准确的数值,但是 LongAdder 最终返回的是一个准确的数值, 所以在一些对实时性要求比较高的场景下,LongAdder 并不能取代 AtomicInteger 或 AtomicLong。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
     /** Number of CPUS, to place bound on table size */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * 单元格表。当非空时,大小为 2 的功率。
     */
    transient volatile Cell[] cells;

    /**
     * 基本值,主要用于没有争用时,但也作为
     * 在表初始化比赛期间的回退。通过 CAS 更新。
     */
    transient volatile long base;

    /**
     * 调整大小和/或创建单元格时使用的旋转锁(通过 CAS 锁定)。
     * 1表示加锁,0表示没加锁
     *  创建和扩容的时候,都是置为1
     */
    transient volatile int cellsBusy;

缓存行伪共享问题

image-20210818144436503

cycle 表示1个时钟周期

image-20210818144507439

CPU3级缓存是将内存中的数据预先读入 缓存,加快访问速度

image-20210818144711613

一个 cell 单位是24 字节(16字节的对象头和8字节的value)

  • core-0 修改 cell[0]
  • core-1 修改 cell[1]

无论谁修改成功,其他的 cell都会缓存行失效, 解决的方法就是 加 padding, 让一个 cell 占用整个缓存行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
*表项属于Cell类;
    原子长填充的一种变体
*(通过@sun.misc.Contended)来减少缓存争用。
    填充
*对于大多数原子来说是多余的,因为它们通常是
*不规则地分散在内存中,因此不会产生太多干扰
*彼此之间。
    但是驻留在数组中的原子对象会
*倾向于相邻放置,大多数也是如此
*经常共享缓存线(具有巨大的负面性能)
*冲击)没有这个预防措施。
前后 加了 128字节的空隙,