LongAccumulator、LongAdder、DoubleAccumulator、DoubleAdder

1,749 阅读3分钟

LongAccumulator

一个或多个变量通过提供的函数共同维护运行时长整型数值。当跨线程竞争更新时,变量集可能会动态增长来减少争用。方法(get或者等效地longValue),在维护的更新变量值中返回当前值。

当多个线程更新用于诸如收集统计信息之类的目的,而不是用于细粒度的同步控制的公共值时,此类通常比AtomiLong更可取。低竞争情况下,这两个类有相似的特征。但在竞争激烈的情况下,此类的预期吞吐量会大大提高,但要消耗更多的空间。

不能保证并且不能依赖于线程内或者线程间的叠加顺序,因此此类仅适用于叠加顺序无关紧要的函数。所供的累加功能应无副作用,因为当尝试更新由于线程间争用而失败时,可能会重新利用该累加器功能。应用该函数时,将当前值作为其第一个参数,并将给定的update值作为第二个参数。比如,要保持最大运行值,你应该提供Long::max以及Long.MIN_VALUE作为标识。

类LongAdder提供了这个类用于特殊维护累加值的函数用法。new LongAdder()的调用相当于LongAccumulator((x,y)->x+y,0L}

这个类继承于Number,但是没有定义诸如equals、hashCode、compareTo方法因为这些实例预期是可变的,所以作为集合的键值是无效的。

public void accumulate(long x){
    Cell[]tempCells;
    long tempBase,value,result;
    int maxIndex;
    Cell operateCell;
    if((tempCells = cells)!=null ||
        (result = function.applyAsLong(tempBase = base, x))! = b && !casBase(tempBase,result){
            //数组不为空,或者数组为空,但是更新base值失败
            boolean uncontended=true;
            if(tempCells == null || (maxIndex = tempCells.length - 1)<0 || (operateCell = tempCells[getProbe() & length]) == null ||
            !(uncontended = (result = function.applyAsLong(value = operateCell.value,x)) == value || operateCell.cas(value,result))){
            //①数组为空 ②数组不为空,但是插槽值为空 ③ 插槽值也不为空,但是插槽值要更新的值与旧值不相同并且该插槽cas操作失败,就去striped64的方法中进行操作
            longAccumulate(x,function,uncontended);
            }
        }
}

返回当前值。返回值不是原子快照,没有并发更新的情况下调用返回准确的结果,但是可能不会合并在计算值时发生的并发更新。(也就是并发时获取值可能不准确)

public long get(){
    Cell[] resultCells = cells; Cell operateCell;
    long result = base;
    if(resultCells != null){
        for(int i=0;i<resultCells.length; ++i){
            if((operateCell = resultCells[i]) !=null)
                result = function.applyAsLong(result,operateCell.value);
        }
    }
    return result;
}

重置变量以保持对标识值的更新。这个方法可能是创建新更新程序的有效替代方法,但仅在没有并发更新时才有效。但是因为这个方法本质上是竞争的,所以仅当已知没有线程在同时更新时才应该使用此方法。

public void reset(){
    Cell[] tempCells = cells;
    Cell operateCell;
    if(tempCells != null){
        for(int i=0;i<tempCells.length; ++i){
            if((operateCell = tempCells[i])!=null){
                operateCell.value=identity;
            }
        }
    }
}

等同于 get()之后再reset().此方法可能使用于多线程计算之间的静态点。如果与此方法同步更新,则返回值不能保证是重置之前发生的最终值。

public long getThenReset(){
    Cell[] tempCells = cells;
    long result = base;
    Cell operateCell;
    base = identity;
    
    if(tempCells != null){
        for(int i=0;i<tempCells.length; ++i){
            if((operateCell = tempCells[i])!=null){
                long value = operateCell.value;
                operateCell.value = identity;
                result = function.applyAsLong(result,value);
            }
        }
    }
    return result;
}

LongAdder与LongAccumulator的对应方法:

add(long x) ==> accumulate(long x)

sum() ==> get()

DoubleAccumulator与DoubleAdder与此类似。只是将Double使用Long保存,操作时将Long转回Double进行计算,最后再保存Long。

Double.doubleToRawLongBits()与Double.longBitsToDouble()进行Double与Long的互转。

总结:

LongAccumulator,LongAdder,DoubleAccumulator,DoubleAdder都继承于Striped64,实现了分段锁的功能。没有线程竞争情况下,效率与AtomicLong相当。一旦多线程竞争,因为Striped64将线程竞争分散到了最多CPU核数数组大小的插槽中各自cas竞争操作,实现了分段的功能,所以效率要远高于Atomic类。