Java LongAccumulator原子累加器源码深度解析

Java LongAccumulator原子累加器源码深度解析,第1张

基于JDK1.8详细介绍了JUC下面的LongAccumulator原子类源码和原理,LongAccumulator是Java8对于原子类的增强。

文章目录
  • 1 LongAccumulator的原理
    • 1.1 LongAccumulator的概述
      • 1.2 LongAccumulator的原理
        • 1.2.1 内部结构
        • 1.2.2 accumulate更新给定值
      • 1.3 其他 *** 作
  • 2 LongAccumulator的案例
  • 3 LongAccumulator的总结

1 LongAccumulator的原理 1.1 LongAccumulator的概述

public class LongAccumulator
extends Number
implements Serializable

LongAccumulator同样是来自于JDK1.8的atomic包,和LongAdder都继承了Striped64,但是LongAccumulator 比LongAdder 的功能更强大。

LongAccumulator 相比于LongAdder,可以为累加器提供非0 的初始值,后者只能提供默认的0 值。另外,前者还可以指定累加规则,比如不进行累加而进行相乘,只需要在构造LongAccumulator 时传入自定义的双目运算器即可,后者则只能是累加规则。

我们在此前学习了LongAdder的源码:Java LongAdder原子累加器源码深度解析,现在我们来学习LongAccumulator的源码。它们的远啊吗和兴部分都是一样的,因此建议先学习LongAdder的源码。

1.2 LongAccumulator的原理 1.2.1 内部结构

LongAccumulator内部具有两个自己的属性,一个LongBinaryOperator类型的双目运算器实例,用于保存累加规则。一个identity用于保存指定的初始值,也是base的初始值,在reset等重置方法调用的时候也会赋值为base的值。

只有一个构造器,可以传递指定的累加规则和指定初始值。

/**
 * 内部的LongBinaryOperator类型的属性,用于保存传入的双目运算器
 * LongBinaryOperator是一个函数式接口,就是对累加规则的封装
 */
private final LongBinaryOperator function;

/**
 * identity用于保存的初始值,也是base的初始值,在reset等重置方法调用的时候也会赋值为base的值
 */
private final long identity;

/**
 * 使用给定的累加器和初始值创建新实例。
 *
 * @param accumulatorFunction 一个双目运算器实例,可以对两个long类型的值进行计算
 *                            如果为null那么在计算时将会抛出NullPointerException
 * @param identity            初始值
 */
public LongAccumulator(LongBinaryOperator accumulatorFunction,
                       long identity) {
    //为function赋值
    this.function = accumulatorFunction;
    //为base和identity赋值
    base = this.identity = identity;
}
1.2.2 accumulate更新给定值

public void accumulate(long x)

具有给定值的更新。作为LongAccumulator的核心方法!

accumulate方法类似于LongAdder的add方法,区别在于可以使用构造器中指定的累加器中的累加规则更新数据。

这里的LongBinaryOperator是一个JDK1.8添加的函数式接口,主要是为了lambda的调用,这个接口封装了对两个long类型参数的 *** 作规则,通过调用applyAsLong方法对传入的参数进行 *** 作并返回 *** 作的结果。

/**
 * 更新方法,与LongAdder的add方法差别在于:
 * 1.调用casBase时LongAdder传递的是b+x,LongAccumulator则使用了r = function .applyAsLong(b = b ase, x) 自定义的规则来计算。
 * 2.调用longAccumulate 时第二个参数LongAdder传递的是null,LongAccumulator传递了function累加器
 */
public void accumulate(long x) {
    Striped64.Cell[] as;
    long b, v, r;
    int m;
    Striped64.Cell a;

    if ((as = cells) != null ||
            //差别1:base要被更新为通过运算器对base和x计算出来的结果,这里也能知道传递的function不能为null
            (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended =
                        (r = function.applyAsLong(v = a.value, x)) == v ||
                                a.cas(v, r)))
            //差别2:第二个参数传递的function累加器,而不是null
            //在longAccumulate方法中,在CAS更新value或者base的时候,会判断function是否为null,即:
            //a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))
            //casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))
            //如果不为null,那么使用制定累加器规则更新
            longAccumulate(x, function, uncontended);
    }
}
1.3 其他 *** 作

public long get()

返回当前值。类似于LongAdder的sum方法,只不过由加法变成了指定的累加规则。

public long longValue()

内部调用 get()方法。

public void reset()

重置维持更新到标识值的变量。类似于LongAdder的reset方法,只不过base值由0变成了在构造器中传递的identity。

2 LongAccumulator的案例

一个指定(a * b / 2)的累加规则的案例如下:

//传递一个累加器LongBinaryOperator实例
LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {
    @Override
    public long applyAsLong(long left, long right) {
        //指定的累加规则 left * right / 2
        //这里的left对应LongAccumulator中的base或者某个Cell的value,这的right对应accumulate传递的参数x
        return left * right / 2;
    }
    //初始值为2
}, 2);
//更新2
longAccumulator.accumulate(6);
//获取结果,应该是6
System.out.println(longAccumulator.get());
3 LongAccumulator的总结

LongAccumulator的功能更加强大,可以指定初始值和累加规则,这样看起来LongAdder更像是LongAccumulator的特例,即初始值为0,累加规则为加法。

因此如果想要使用longAccumulator实现LongAdder的功能,那么我们手动将累加规则指定为加法,并且identity指定为0即可:

LongAccumulator longAccumulator = new LongAccumulator(new 
LongBinaryOperator() {
    @Override
    public long applyAsLong(long left, long right) {
        //返回left+right,这样累加规则就和LongAdder一样了
        return left + right;
    }
    //初始值为0
}, 0);
//更新6
longAccumulator.accumulate(6);
//更新1
longAccumulator.accumulate(1);
//获取结果,应该是7
System.out.println(longAccumulator.get());

可以看到我们初始化LongAccumulator对象的代码量还是比较多的,特别是创建匿名对象的代码。上面我们说过这个LongBinaryOperator是一个函数式接口,因此我们推荐使用lambda表达式的写法:

LongAccumulator longAccumulator = new LongAccumulator((left, right) -> {
    return left + right;
}, 0);

更进一步,我们可以使用方法引用:

LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

它们的含义都是一样的,即采用加法运算,但是代码量却简单了许多,所以lambda还是很有必要学习的!

相关文章:

  1. Unsafe:JUC—Unsafe类的原理详解与使用案例。
  2. volatile:Java中的volatile实现原理深度解析以及应用。
  3. CAS:Java中的CAS实现原理深度解析与应用案例。
  4. 伪共享:Java中的伪共享深度解析以及避免方法。
  5. JMH:Java使用JMH进行方法性能优化测试。

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/785940.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-04
下一篇 2022-05-04

发表评论

登录后才能评论

评论列表(0条)

保存