소스코드를 바탕으로 기본적인 구현과정을 분석해보겠습니다.
세밀한 동기화 제어가 아닌 통계 수집 등의 목적으로 사용되는 공통 합계를 여러 스레드에서 업데이트하는 경우 일반적으로 이 클래스가 AtomicLong보다 선호됩니다. 경합이 발생하면 이 클래스의 예상 처리량은 더 높은 공간을 소비하는 대신 훨씬 더 높습니다.
위 단락은 LongAdder
의 소스 코드 주석의 일부입니다. 번역하면 LongAdder
源码注释中的一部分,翻译过来意思大概是
当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong
。 在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
也就是说LongAdder
在并发度高的情况下效率更高,但是代价是以空间换时间。
通俗地解释一下LongAdder
的原理:当并发少的时候,累加操作只在一个变量base
上执行就够用了,所以和AtomicLong
类似;但是当并发量上来的时候,如果还是在变量base
上进行操作就会有很多线程阻塞,所以就创建一个数组cells
,在数组的每一个元素上都可以进行累加,最后计算结果时再就算一下base
和cells
数组每个元素的和就行了。而线程具体在数组的哪一位进行操作可以通过计算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
语句,初始状况下cells
是null
,所以会进行casBase
操作,也就是在base
变量上进行累加,如果操作成功了说明当前没有竞争,所以就结束了。
当并发量上来的时候,进行casBase
方法就有可能会失败,所以这时进入第二个if
语句判断。
第一次进来时Cell
数组as
是null
,所以就会执行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>base
및 cells
배열의 각 요소의 합을 계산하면 됩니다. 스레드가 작동하는 배열의 특정 비트는 해시
를 계산하여 인덱스 위치를 결정함으로써 확인할 수 있습니다. 🎜🎜소스 코드 소개🎜🎜LongAdder
는 상위 클래스 Striped64
에서 상속된 속성입니다. 여기서 Cell
은 누적 작업에 사용되는 내부 클래스입니다. . 에는 누적된 값을 저장하는 내부 value
속성이 있습니다. 🎜rrreee🎜누적 연산 메서드 increment()
는 실제로 add(1L)
를 호출하므로 add
메서드를 직접 살펴보겠습니다🎜rrreee🎜먼저 첫 번째 if
문을 보면 처음에는 cells
가 null
이므로 casBase
작업이 수행됩니다. 마찬가지로 base
변수에 누적하는 것입니다. 작업이 성공하면 현재 경쟁이 없다는 의미이므로 종료됩니다. 🎜🎜동시성이 증가하면 casBase
메서드가 실패할 수 있으므로 이때 두 번째 if
문 판단이 들어갑니다. 🎜Cell
배열 as
가 null
입니다. 이므로 longAccumulate
를 실행하고 Cell
배열을 as
로 초기화하고 인덱스 1
에 1;🎜🎜<li>🎜이 <code>if
문 as
를 실행한 후에는 null
이 아니며 배열 길이도 더 길어집니다. 0a = as[getProbe() & m]) == null
보다 이 문장을 간단히 이해하면 배열 as 코드>에서 인덱스 위치를 무작위로 찾아 해당 위치의 값이 <code>null
인지 확인합니다. null
인 경우 longAccumulate
를 실행합니다. >, 그렇지 않은 경우 >null
계속 하향 판단🎜🎜!(uncontended = a.cas(v = a.value, v + x))
이 문장은 발견된 인덱스 위치에 대해 누적 연산을 수행한다는 의미입니다. 실패하면 longAccumulate
🎜🎜🎜를 실행합니다.위 내용은 Java 동시 프로그래밍의 LongAdder 소스 코드 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!