Java并发编程之LongAdder源码(三)
前言
前面两篇文章介绍了LongAdder的主要方法实现流程,除此之外还有两个需要注意的地方,分别是:
sum()方法@sun.misc.Contended注解
sum方法
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
上面就是sum()方法的源码,通过前面两篇文章我们已经知道了LongAdder可能会在base和Cell数组两个地方进行累加操作,所以sum()方法就是对这两个的值进行求和。
long sum = base先获取base的值,然后判断Cell数组as是否为null,不为null就循环遍历数组每个元素,然后把值都加到sum上并返回。
@sun.misc.Contended

上图是CPU缓存设计概览,由于CPU和内存之间速度的差异太大,因此在CPU中设计了L1、L2、L3三级高速缓存,大小依次变大,速度依次变慢,其中L1、L2是在每个内核中,而L3是所有内核共享。
CPU读取数据时,首先会从缓存中去读,如果缓存中没有的话会去内存先读到缓存中,然后再从缓存中取数据。而CPU从内存中读取数据时并不是按字节或者单个字来读取的,因为根据局部性原理(一块内存被访问后,它附近的地址也很有可能会被访问),所以CPU读取的是一整块内存(64字节),然后保存到L3中,这一块内存被称为缓存行(Cache Line)。
而由于L3是所有内核共享的缓存,所以当一个内核修改了缓存行中的数据后,为了保证数据的一致性,会使其他内核中读取的该缓存行数据失效,就不得不从主存中重新读取数据,这种现象被称为伪共享。
为了解决伪共享的问题,可以采用“字节填充”的技术,其原理是通过在数据中填充空字节,尽可能让不相干的数据分布到不同的缓存行中,这样不同的CPU读取的数据就在不同的缓存行了,避免了伪共享问题的发生。
LongAdder之所以在高并发情况下效率会很高,主要是因为通过把累加操作分散到Cell数组中每个元素上去,最后再进行求和来计算结果。
@sun.misc.Contended
static final class Cell {
// 省略...
}
在Cell类上的注解@sun.misc.Contended 的作用就是通过字节填充避免缓存伪共享的产生,所以多个CPU操作Cell数组时会大大提高计算效率。
转载自:https://juejin.cn/post/7227038198408003641