文章目录基于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的总结
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还是很有必要学习的!
相关文章:
- Unsafe:JUC—Unsafe类的原理详解与使用案例。
- volatile:Java中的volatile实现原理深度解析以及应用。
- CAS:Java中的CAS实现原理深度解析与应用案例。
- 伪共享:Java中的伪共享深度解析以及避免方法。
- JMH:Java使用JMH进行方法性能优化测试。
如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)