>  기사  >  Java  >  Java 동시 프로그래밍의 LongAdder 소스 코드 분석

Java 동시 프로그래밍의 LongAdder 소스 코드 분석

PHPz
PHPz앞으로
2023-05-28 08:22:171288검색

머리말

소스코드를 바탕으로 기본적인 구현과정을 분석해보겠습니다.

세밀한 동기화 제어가 아닌 통계 수집 등의 목적으로 사용되는 공통 합계를 여러 스레드에서 업데이트하는 경우 일반적으로 이 클래스가 AtomicLong보다 선호됩니다. 경합이 발생하면 이 클래스의 예상 처리량은 더 높은 공간을 소비하는 대신 훨씬 더 높습니다.

위 단락은 LongAdder의 소스 코드 주석의 일부입니다. 번역하면 LongAdder源码注释中的一部分,翻译过来意思大概是

当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong。 在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。

也就是说LongAdder在并发度高的情况下效率更高,但是代价是以空间换时间。

通俗地解释一下LongAdder的原理:当并发少的时候,累加操作只在一个变量base上执行就够用了,所以和AtomicLong类似;但是当并发量上来的时候,如果还是在变量base上进行操作就会有很多线程阻塞,所以就创建一个数组cells,在数组的每一个元素上都可以进行累加,最后计算结果时再就算一下basecells数组每个元素的和就行了。而线程具体在数组的哪一位进行操作可以通过计算hash来确定索引位置。

源码简介

LongAdder从父类Striped64继承过来的属性,这里的Cell是一个用来进行累加操作的内部类,内部有一个value属性来存储累加的值。

// CPU核心数
static final int NCPU = Runtime.getRuntime().availableProcessors();
// 并发高时进行累加的Cell数组
transient volatile Cell[] cells;
// 多个线程没有竞争时在base上进行累加
transient volatile long base;
// Cell数组是否正在创建或扩容
transient volatile int cellsBusy;

累加操作方法increment()实际调用的是add(1L),所以我们直接来看add方法

public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true; // 表示没有竞争
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

首先来看第一个if语句,初始状况下cellsnull,所以会进行casBase操作,也就是在base变量上进行累加,如果操作成功了说明当前没有竞争,所以就结束了。

当并发量上来的时候,进行casBase方法就有可能会失败,所以这时进入第二个if语句判断。

  • 第一次进来时Cell数组asnull,所以就会执行longAccumulate,对Cell数组as进行初始化并且在索引1位置累加1

  • 之后再执行到这个if语句as就不是null了,而且数组长度也大于0

  • a = as[getProbe() & m]) == null,这句话简单的理解就是在数组as中随机找到一个索引位置,判断该位置的值是不是null,如果是null的话就执行longAccumulate,不是null继续向下判断

  • !(uncontended = a.cas(v = a.value, v + x))这句话的意思是,在找到的这个索引位置进行累加操作,如果成功了就结束操作,如果失败了就执行longAccumulate

    을 의미합니다. 많은 스레드가 통계를 수집하는 데 사용되지만 세밀한 동기화 제어 목적이 아닌 공통 합계를 업데이트하는 경우 일반적으로 이 클래스가 AtomicLong보다 선호됩니다. 낮은 업데이트 경합에서 이 두 클래스는 비슷한 특성을 갖습니다. 그러나 경합이 높은 경우 이 클래스의 예상 처리량은 훨씬 높지만 공간 소비가 더 높아집니다.
즉, 동시성이 높을 때 LongAdder가 더 효율적이지만 비용은 시간과 공간을 교환하는 것입니다. 🎜🎜LongAdder의 원리를 간단하게 설명하세요. 동시성이 거의 없는 경우 하나의 변수 base에 대해서만 누적 연산을 수행하면 충분하므로 AtomicLong 과 동일합니다. 그러나 동시성이 증가할 때 여전히 base 변수에서 작업하면 많은 스레드가 차단되므로 cells 배열을 만듭니다. code>, 배열의 각 요소에 대해 누적을 수행할 수 있습니다. 최종 결과를 계산할 때 <code>basecells 배열의 각 요소의 합을 계산하면 됩니다. 스레드가 작동하는 배열의 특정 비트는 해시를 계산하여 인덱스 위치를 결정함으로써 확인할 수 있습니다. 🎜🎜소스 코드 소개🎜🎜LongAdder는 상위 클래스 Striped64에서 상속된 속성입니다. 여기서 Cell은 누적 작업에 사용되는 내부 클래스입니다. . 에는 누적된 값을 저장하는 내부 value 속성이 있습니다. 🎜rrreee🎜누적 연산 메서드 increment()는 실제로 add(1L)를 호출하므로 add 메서드를 직접 살펴보겠습니다🎜rrreee🎜먼저 첫 번째 if 문을 보면 처음에는 cellsnull이므로 casBase 작업이 수행됩니다. 마찬가지로 base 변수에 누적하는 것입니다. 작업이 성공하면 현재 경쟁이 없다는 의미이므로 종료됩니다. 🎜🎜동시성이 증가하면 casBase 메서드가 실패할 수 있으므로 이때 두 번째 if 문 판단이 들어갑니다. 🎜
  • 🎜처음 들어왔을 때 Cell 배열 asnull입니다. 이므로 longAccumulate를 실행하고 Cell 배열을 as로 초기화하고 인덱스 11;🎜🎜<li>🎜이 <code>ifas를 실행한 후에는 null이 아니며 배열 길이도 더 길어집니다. 0
  • 🎜🎜
  • 🎜a = as[getProbe() & m]) == null보다 이 문장을 간단히 이해하면 배열 as 코드>에서 인덱스 위치를 무작위로 찾아 해당 위치의 값이 <code>null인지 확인합니다. null인 경우 longAccumulate를 실행합니다. >, 그렇지 않은 경우 >null계속 하향 판단🎜🎜
  • 🎜!(uncontended = a.cas(v = a.value, v + x))이 문장은 발견된 인덱스 위치에 대해 누적 연산을 수행한다는 의미입니다. 실패하면 longAccumulate🎜🎜🎜를 실행합니다.

위 내용은 Java 동시 프로그래밍의 LongAdder 소스 코드 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제