Java源码系列(25) -- LongAdder

June 8, 2020

一、类签名

利用一个或多个变量共同维护一个初始值为 0long 总和。当调用 add() 时出现线程竞争,这些变量集分别动态增加,减少对同一个锁的竞争。

方法 sum() 或变量 longValue,返回当前用于维护总和变量集的总大小。

1
public class LongAdder extends Striped64 implements Serializable

在多线程下更新总和值,例如进行统计数据的收集,而不是用于细粒度的同步控制,相比 AtomicLong,此类是更好的选择。

在较少竞争的情况下,此类和 AtomicLong 特性基本相似。当在高竞争的情况下,本类的吞吐量明显更高,同时也消耗更多内存空间。

LongAdder_UML

LongAdders 能和 ConcurrentHashMap 一起使用去维护一个频繁伸缩的 map。例如,添加计数值到ConcurrentHashMap<String, LongAdder> freqs,键不存在时进行初始化,可通过freqs.computeIfAbsent(key, k -> new LongAdder()).increment();实现。

此类继承自 Number,但没有定义如 equalshashCodecompareTo 等方法,因为实例会发生变化,所以不能用作集合的键。源码版本 JDK11

二、构造方法

默认构造方法,类完成构造后初始总和为 0

1
2
public LongAdder() {
}

三、成员方法

此方法能把指定参数值 x 增加到目标值上。如果该参数传递负数,则意味着从总数上减去指定值。

1
2
3
4
5
6
7
8
9
10
11
public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            // 调用父类的Striped64()
            longAccumulate(x, null, uncontended);
    }
}

以下两个方法通过调用 add() 方法实现数值递增和递减。递增传递的参数是 1L,递减传递的参数是 -1L

1
2
3
4
5
6
7
public void increment() {
    add(1L);
}

public void decrement() {
    add(-1L);
}

返回当前的总计数值。返回的值不是原子性的快照,就是一个基本类型的 long。在没有并发更新的情况下调用会返回准确结果。但是在计算总和时发生的并发更新,可能不会被算到总和内。

1
2
3
4
5
6
7
8
9
10
11
12
13
public long sum() {
    // 从父类获取cells
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                // 从cell获取记录值并累加到sum
                sum += c.value;
    }
    // 返回sum的值
    return sum;
}

重置维护总计值的变量值到 0。对比创建一个新的实例,通过此方法重置总计值后复用实例,是个更好的选择,但也仅在更新的时候,没有其他线程并发添加值。

因为这个方法很活跃,只能在确认没有并发更新的时候使用。

1
2
3
4
5
6
7
8
9
10
11
public void reset() {
    // 从父类获取cells
    Cell[] cs = cells;
    base = 0L;
    if (cs != null) {
        // 逐个重置cell保存的值
        for (Cell c : cs)
            if (c != null)
                c.reset();
    }
}

方法获取总和后重置原始值为 0,并把保存的值作为结果返回,相当于 sum()reset() 的合并调用。

如果调用此方法的同时,还有其他线程在进行更新操作,此方法的 返回值重置前存储的值 不保证是一致的。因为先返回值,值在其他线程继续修改,最后才被重置为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public long sumThenReset() {
    // 从父类获取cells
    Cell[] cs = cells;
    // 获取现在的总计值
    long sum = getAndSetBase(0L);
    if (cs != null) {
        for (Cell c : cs) {
            if (c != null)
                // 获取总值并重置cell
                sum += c.getAndSet(0L);
        }
    }
    // 返回sum的值
    return sum;
}