LongAdder jdk8 새로운 카운터가 높은 동시성 경우 목표, 동시에 환경을위한, 대신 AtomicLong / AtomicInt, 효율적인 높은 동시성 조건에 대한 보편적 인 카운터가됩니다.
높은 동시 카운트에서 일반적으로 먼저 기계 명령어를 직접 지원하는 로킹 피하기 위해 갱신 카운터 값과 하드웨어 레벨 명령들을 사용 AtomicLong / AtomicInt, AtmoicXXX CAS 생각해야 효율도 높은 것이다. 그러나 스레드의 경우에 경쟁의 CAS 작업 AtomicXXX이 스레드는 하나 개의 스레드 만 각 CAS가 성공할 수 있기 때문에 경쟁이 매우 스레드의 실패가 될 것입니다, 많은 동시 경우, 한 번에 간단하게주기를 실패합니다. 더 많은 실패, 더 많은 사이클, 가까이 스핀 락 (스핀 락)에 CAS 작업의 많은 스레드. 카운트 동작은 매우 간단한 조작 수 있었다, 그것은 실제 CPU 시간이 AtomicXXX 높은 동시성 수에서의 CPU 많은 시간이 스핀에 낭비되고, 가능한 한 적게해야한다, 이것은 낭비뿐만 아니라 감소 실제 계산 효율.
코드 // jdk1.8 AtomicLong 구현의 sun.misc.Unsafe의 코드
CAS 거짓 여러 번 연속 반환의 조건을 결정하는 동안 스레드가, 경쟁이 매우 치열 //이 쓸모없는 루프의 원인이됩니다 오버 헤드 본질적으로 높은 휘발성 변수주기를 읽는
//이 때문에, 높은 동시성, AtomicXXX의 계산 방법이 바람직 그렇지 않다
{공공 최종 긴 getAndAddLong (긴, 긴 델타 오프셋 개체 O)
롱 V는;
할 {
V = getLongVolatile는 (O는 오프셋 (offset));
}는 동안 (compareAndSwapLong (O는 오프셋, V, V + 델타)!)
반환 V;
}
보다 높은 동시성보다 AtomicLong 더 효율적 LongAdder는 말을 말을 기준으로 그것은? 그 다른 스레드가 다른 계수 유닛에있을 수 동시 DAMA 계수 유닛 카운트를 유지하는 기이며, 로크 세그먼트 달성 - 기본 원리는 LongAdder ConcurrentHashMap의 이러한 동시 설계 클래스이며 따라서 스레드 경합을 줄이고, 계산 및 동시성 효율을 향상시킬 수 있습니다. 기본적으로 생각하는 시간을위한 공간, 그러나 무시할 높은 동시성의 경우에서 소비되는 실제 공간.
높은 동시성 수를 처리 할 때 지금, 선호 LongAdder 수보다는 AtomicLong를 계속 사용해야합니다. 물론, 낮은 스레드 카운트 경쟁, 또는 원자는 간단하고 직접적인 사용에서 수행하고, 효율이 약간 더 높다.
이러한 생성 된 일련 번호와 같은 다른 상황이이 경우에 필요한 정확한 값은 전역 적으로 고유 AtomicLong는 현재 사용 LongAdder 안, 최적의 선택이 될 것입니다.
ConcurrentHashMap의와 LongAdder에서 소스 코드의 다음의 간단한 분석으로,이 클래스의 소스 복잡 보이지 않는 (LongAdder보다 1.6 및 1.7처럼, 당신은 1.7 볼 수 있습니다).
먼저, 관계 클래스
Striped64 코어 동작, 64 비트 데이터 처리의 일부를 구현하는 코어는 쉽게 다른 기본 유형으로 변환 할 수있는 공통의 부모 클래스 구현은 일반 클래스이다. 이진 산술 축적, 당신은이 클래스의 바이너리 연산 모드, 산술 계산으로 귀하가 제공하는 방법을 제공하기 위해 참조하고 결과를 저장 할 수 있습니다. 이진 연산자 제 피연산자는 계수 유닛의 어큐뮬레이터의 현재 값이며, 추가의 값을 외부 적으로 제공된다.
몇 가지 예 :
각각의 동작은 원래의 값을 더한 소정 값 (X, Y)의 다음 이진 동작 요구한다고 가정 -> X , + Y를 그래서 어큐뮬레이터 디지털 번호 y를 제공 할 때마다 추가하는 실질적으로 동일한 기능이 LongAdder]
-> X 각각의 동작을 가정의 원래의 값이 그것의 배수가된다 필요 이것은 이진 동작 (X, Y)로 지정 될 수있다 * Y, 어큐뮬레이터 ; 디지털 Y를 제공 할 때마다 승산, Y는 통상적으로 매 2 배로 칭한다 =
그리고, 각 동작을 가정하면 그 값은 원래 다섯 번하게 요구 및 3 두 뺀 넷, 다음 곱셈 소정 수를 더한 마지막 6, (x, y)의 진 동작에 의해 분할 -> ( (X * 5 + 3) / 2 - 4) * Y +6, 말 같은 축적 동작을 수행 할 각 어큐뮬레이터;
......
LongAccumulator 표준 구현 클래스 LongAdder 전문화되어 구현 클래스가, 그 기능은 동일하다 LongAccumulator ((X, Y) -> X + Y, 0L). 이들 사이의 차이가 매우 간단하고, 전자가 추가 연산 또는 두 종류를 뺄 수있는 이진 연산 일 수있다.
더블, 롱 버전 상대적으로 큰 변화는, 기본 데이터 및 Double.longBitsToDouble Double.doubleToRawLongBits를 <---> 이중 변환에 8 바이트 오래 유형 긴 저장 시간을 사용하는, 계산 버전은 롱의 간단한 버전 수정 더블로 변환 할 때. 동작 sun.misc.Unsafe CAS INT에만 제공되기 때문이고, 길이, 오브젝트 타입 (참조 또는 포인터)이이 동작을 제공하고, 다른 종류의 CAS 조작 될 수있는 이러한 세 가지 유형으로 변환되어야한다. 여기서 긴 형태는 8 바이트의 기본 유형 수만큼 종류가 무의미로 가져가 고려 될 수있다. 자바는 대신 가까운 긴 타입, C 언어 무효 *이 지정되지 않은 (또는 불리는 원시 형)에 있지 않습니다.
그들은 LongAdder 클래스를 이야기 위의 두 문장에 네 구현 클래스 구분.
둘째, 핵심 구현 Striped64
Striped64의 네 가지 핵심 클래스의 구현은,이 클래스가 사용하는 동시 압력 동일한 주에하려고 생각을 분할. 마찬가지로 ConcurrentHashMap.Segment 1.7 이전 버전라는 클래스 셀 통상 이진 산술 누적 유닛 Striped64의 사용은, 스레드 해시 복용 셀을 집적하여 성형 동작에 매핑된다. 안전하지 않은 작동을 기초 다수의 사용을 제공하면서 모듈로 연산 효율뿐만 아니라, 어레이의 크기를 촉진하기 위해, 셀 2 ^ N으로 설정된다. 1.7 배럴, 쉽게 많은 ConcurrentHashMap의의 기본 실현.
1, 셀 축적 장치가
여기에 본, 나는 겉으로는 간단한 질문을 생각 : 셀은 간단 있도록 만 긴 형태의 변수, 왜 그냥 긴 값을 사용하면?
어레이는 그 자체가 매우 간단하므로 휘발성 요소를지지 할 CAS 오프셋 계산 동안에 초기에 안전하지 않은 작업이 매우 강력한 제공 휘발성 어레이의 판독 및 기록 소자 일 수있다. 문제는 다음 지점에 응답합니다.
단순화 된 AtomicLong로 볼 수있다 // 매우 간단한 클래스
// 값 캐스 작업의 값을 업데이트하는
가짜 방지하기 위해 캐시 라인 채우기를 사용하는 대신에, 하이 엔드 노트 // @ sun.misc.Contended입니다 공유, 당신은 내가 정교하지 않습니다 자신의 온라인 검색 낮출 수
@는 셀 {정적 클래스 최종 sun.misc.Contended
휘발성 긴 값을,
셀합니다 (X-롱) {값 = X 축를;}
마지막 부울 CAS를 (CMP 긴, 긴 발 ) {
UNSAFE.compareAndSwapLong합니다 (이, valueOffset, CMP, 발)을 반환;
}
// 안전하지 않은 기계를 초기화 안전하지 않은 관련된
개인 정적 최종 sun.misc.Unsafe 안전하지 않은,
개인 긴 valueOffset 최종 정적;
정적 {
시도 {
안전하지 않은 = sun.misc.Unsafe.getUnsafe ();
클래스 AK = Cell.class <?>;
valueOffset = UNSAFE.objectFieldOffset (ak.getDeclaredField ( '값'));
} 캐치 (예외 전자) {
새로운 오류 (예)를 던져;
}
}
}
2, Striped64主体代码
{클래스 Striped64이 (가) 추상 수를 확장
@ 최종 정적 셀 클래스를 sun.misc.Contended {...}
/ ** 표 장소의 크기 * / 결합의 수 CPU를,
(정적 INT NCPU = 최종 Runtime.getRuntime (). availableProcessors) ;
// 셀 어레이 (2)의 길이가 ^ N, 배열 ConcurrentHashMap의 jdk1.7의 세그먼트와 유사 할 수있는 경우와 같은
과도 휘발성 셀 [] 세포,
두 경우에 사용될 수있다 축적 기의 // 기본값 :
// 1, 발생에는 동시 조건, 기본의 직접 사용이없는, 빠른;
// 2, 멀티 스레드 배열 초기화 테이블, 우리는 해당 테이블의 배열이 때문에 하나 개의 스레드가 성공적으로 경쟁 할 수있는, 한 번만 초기화 이러한 상황을 확인해야합니다 경쟁 스레드는 자료에 축적 작업을 시도 실패
과도 휘발성 롱 자료;
// 세포가 초기화 스핀 식별, 또는 이후의 확장, 1 (통화 중, 통화 중이 CAS 작업 식별자의 필요성 식별 시청 제한)에 직접 잠금 해제에 대응하는, 분주 cellsBusy = 0을 취소하는데 사용될 수
과도 휘발성의 INT cellsBusy;
Striped64 () {
}
// CAS를 사용하여 기본 값으로 업데이트
최종 부울 casBase (CMP 긴, 긴 발을) {
반환 UNSAFE.compareAndSwapLong합니다 (이, BASE, CMP, 발);
}
// CAS는 세포 스핀 업데이트 1 식별하는 데 사용됩니다
// 업데이 트를 0시 CAS가 광고 직접 cellsBusy에없는
최종 부울 casCellsBusy () {
UNSAFE.compareAndSwapInt (이 본, cELLSBUSY 0 ,. 1)을 반환;
}
// 두 ThreadLocalRandom 방법은 패키지 액세스와의 관계 때문에, 다소 방법에 따라, 여기에 다시 쓰고, 다시
// 프로브는 프로브 / 프로브 /이 프로브, 이해하기 어려운, 그것은, 속성 ThreadLocalRandom의 내부에 번역
//하지만이 스레드로 이해 될 수있는 Striped64의 이해에 영향을주지 않습니다 해시 값 자체는
최종 getProbe는 () {int로 정적
UNSAFE.getInt (는 Thread.currentThread () 프로브)를 반환;
}
// 대응 개작 스레드를 통해 해시 값을 재 계산
최종의 INT advanceProbe 정적 (INT 프로브) {
프로브 프로브 ^ = 13 <<이다 // xorshift
프로브 프로브 >>> ^ = 17].
프로브 프로브 << = ^ 5].
UNSAFE.putInt (는 Thread.currentThread (), 프로브, 프로브 )
창 프로브;
}
/ **
외부 CAS 업데이트 된 기준 값은, 셀 = NULL는 CAS가 모듈에 대응하는 해시 값을 갱신! * 코어 방식을 실현하는 동안, 방법 추천 CAS 작업 (세포 = NULL Cell.Value)
* @param 번째 피연산자 X 외부 설치 작업의 수이며, 이진 동작에있어서 나 앞에서 값
갱신 기능 FN * @param을 또는 추가에 대한 널 (본 컨벤션을 피가 LongAdder에서 또는 함수)에서 추가 필드에 대한 필요.
* 이진 산술 연산이 외부에서 제공하고 지주의 하나의 인스턴스 만, 수명주기에 걸쳐 동일하게 유지, 널 대신 LongAdder이 특별한 있지만, 가장 일반적인 경우, 하나의 메소드 호출을 감소
CAS 전에 실패 할 경우 파라미터 : 거짓 wasUncontended 전화 발신자 CAS 작업 전에 전화 실패한 것을 나타 내기 위해서 (때문에), 경우는 false
* /
최종 무효 longAccumulate합니다 (X-롱을 LongBinaryOperator wasUncontended 부울에서 Fn) {
INT H;
//이 경우 상당히 상기 0 이외의 해시 값을 생성하는 스레드 수득
{IF ((H = getProbe ()) == 0)
; // 초기화 포스 ThreadLocalRandom.current ()를
H = getProbe ();
wasUncontended = TRUE로;
}
거짓 부울 =을 충돌하는 단계; // 전지 유닛의 경우에 true 널 아닌 모듈 해시 맵 해당 얻어진 경우 마지막 슬롯에 비어 있지 않은 경우,이 값이 이해 기분 팽창 의도로 간주 될 수있다
{(;;)에 대한
세포 []로서, 셀 a 및 N-INT 롱 V]
(! (= 세포 AS) = NULL && (= N-as.length)> 0) {// IF 셀 초기화 된
경우 (같은 (a = ( -) == null의 n이 1) H]) {// 세포 상기 맵을 획득 모듈 해시 부는 널 (또는 널이 사용되지 않은)
IF (cellsBusy == 0) 어떠한 스레드 확장을 실행하지 않는 경우 {// 새로운 셀을 첨부하려고
셀 R = 새로운 셀 (X)을 // 낙관적 만드는 새로운 축적 부 생성
(cellsBusy == 0 && casCellsBusy (있다면 )) {// 로크하려고
= 거짓 부울 위해 만들어;
잠긴 경우 재확인 아래에서 시도 {// 잠금 재확인 전에 결정
INT m, J; 셀 [] RS
! IF ((RS = 세포) = NULL && (m = RS한다. 길이)> 0 && RS [J = (m - 1) H] == NULL) {//는 팽창을 할 수있는 다른 스레드를 고려 재 할당은 여기서 다시 판단
RS [J] = R // 셀 유닛 축적 동작을 수행하는 데 사용되지 않는다 (제 1 과제는 동작의 누적 수와 상기베이스에 상당하고 합산 동작이 얻어진다 수행 할 때 실제 결과)
를 true로 등재 =;
}
} {최종적으로
cellsBusy = 0; 명확한 식별 스핀, 분리 잠금
}
IF 원래가 null의 휴대 장치는 그 자체로 첫 번째 축적 작업을 수행하는 경우, 작업이 완료되었습니다 // (작성),이 루프를 종료 할 수 있습니다
휴식을 ;
계속; // 슬롯은 지금이 처음이 아니다 그들의 축적 동작을 비 비어 시작
}
}
에서 충돌 = false로,이 하나의 // 구현은 세포가 처음을 계속할 수 없습니다 때문입니다 잠겨 할당 연산자 (제 1 적산) 때문에 고려되지 않을 수 팽창
}
다른 IF (WasUncontended!) 이미 (누적 번) 시도가 발생한 스레드가 경쟁을 보여 실패 CAS의 업데이트 a.value 앞에 실패로 알려진 // CAS
wasUncontended = 진실을; // 재탕 식별하는 데 실패 후 계속 다시 재 스레드 다시 해시 값 계산
다른 경우 (a.cas (V = a.value // ((FN == NULL) V + X :? fn.applyAsLong (V, X가)))) CAS 시도 업데이트 a.value (누적 번) ------이 지점이 표시
BREAK를; // 성공적으로 누적 작업을 완료, 루프 종료
(! N> = NCPU || 세포 등을 =) 다른 // 세포의 경우 배열 이미 가장 큰, 또는 중간 확장 작업까지. NCPU 반드시이 아니기 때문에 ^ n은, 그래서 여기에> =와
에서 충돌 = false로; // 부실 최대 크기 또는 n의 길이 증가,이 지점의 실행은, N> = NCPU 항상 true가됩니다의 다른 두 가지를 다음과 같은 설명 이 실행되지 않을 경우, 다시 확장 없을 것
// CPU 병렬 연산들의 최대 수, N> = NCPU 다시 여러 (전체 원자 캐시를 잠궈 확보 할 CMPXCHG 다핵 필요 CAS 대응 x86 명령어에있다)의 코어의 CAS 번호 일 수있다 셀 CAS 대회에서 같은 결과로 맵핑 스레드 정말 사업의 확장과 관련,이 냄비의 해시 값 전적으로
(!에서 충돌) // 매핑 된 셀 단위가 null가 아닌 경우, 다른 사람은, 그것은을 주려고 노력한다 축적 된 경우, CAS 경쟁, 확장이 true 의도로 설정되어 이번에 실패
루프 // 경쟁을 나타내는 것은 심각한 이것과 같은 여전히 있다면, 실제 확장을 통해 다음 번에
, 확장의 진정한 의도에 // 설정;에서 충돌 사실 =을 여기 만 진정한 충돌, 및 이러한 일을 수행하기 위해 할당됩니다,이 경우 다른 용량 확장 뒤에 수행 할 수있다
(cellsBusy는 == 0 && casCellsBusy 다른 경우 ()) {// 다음 확장을 고려,이 단계로 올 수 설명 확장 ------ 표시된 지점 B에 대한 잠금을 시도, 경쟁이 매우 치열
은 try {
{// 낮은 테이블을 확장 (== 세포와 같은) 경우 확장이 다른 스레드인지 여부 부실 검사 (CAS 업데이트 잠금 표준 않는 한 지식의 ABA 문제를 처리하고 여기에 다시 확인하지 못했습니다)
세포 [] 중계국 = 새로운 셀 [ N << 1]; // 확장 두번 수행
에 대해 INT (I = 0; I <N-; I ++)
RS [I] AS = [I]
셀 RS =;
}
최종적 {}
cellsBusy = 0; // 박리 잠금
}
에서 충돌이 = FALSE로 // 확장 경향이 거짓이고
계속 // 재시 확장 테이블 전개 후 다시 시작
}
// 스레드에 재 해시를 생성하고; H = advanceProbe (H)의 값, 해시 충돌을 감소, 감소 된 상황은 동일한 셀 CAS 원인 경쟁에 대응
}
(cellsBusy == 0 && 세포를 다른 경우 && casCellsBusy로는 == ()) {// 세포가 고정되지 않고, 초기화되지 않은, 다음이 다른 경우 잠금이 성공에이었다 잠금을 시도
부울 초기화 = 거짓으로,
은 try {// 초기화 테이블
IF (== 세포 AS) {// CAS 불가피한 문제 다시 검출 ABA, 경우, 또는 널 (null) 또는 빈 배열하고 수행 초기화
세포 [] RS = 새로운 셀 [ 2] // 초기화 단지 두 부 때 생성
중계국 - [1] 새로운 셀 = (X) H; // 한 누적 연산 단위에 관계없이 다른, 널 계속
세포 = RS;
초기화 = TRUE로;
}
} {최종적으로
cellsBusy = 0; // 명확한 식별 스핀, 분리 잠금
}
원래가 null 셀 단위가 스스로 축적 처음 작동 할 경우 (초기화)가 // 작업이 완료되어있는 경우, 루프를 종료 할 수
BREAK를;
}
다른 IF (casBase (V = 자료, ((에서 Fn == NULL) V + X :? fn.applyAsLong (V, X가)))) // 셀이 초기화 될 때, 상기베이스 상에 직접 작동 축적하려고
BREAK, 직접 염기를 사용하여베이스 상 후퇴 // 축적 작업이 성공, 작업의 완료는, 루프를 철회 할 수 있습니다
}
}
// 두 번 더 긴 실질적으로 동일한 논리 말하지
최종 무효 doubleAccumulate (더블 X, DoubleBinaryOperator FN, 부울 wasUncontended는);
//은 안전하지 않은 기계를 안전하지 않은 초기화
; 최종 정적 안전하지 않은 개인 sun.misc.Unsafe
개인 롱 최종 정적 기지,
개인 긴 CELLSBUSY 최종 정적을;
개인 정적 최종 오래 PROBE;
정적 {
시도 {
안전하지 않은 = sun.misc.Unsafe.getUnsafe ();
클래스 SK = Striped64.class <?>;
BASE = UNSAFE.objectFieldOffset
(sk.getDeclaredField ( "기지국"));
CELLSBUSY = UNSAFE.objectFieldOffset
(sk.getDeclaredField ( "cellsBusy"));
클래스 TK = Thread.class 등 <?>;
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField ( "threadLocalRandomProbe"));
} 캐치 (예외 전자) {
새로운 오류 (예)를 던져;
}
}
}
셀은 간단하므로, 왜 그냥 긴 값을 사용하면이 내가 질문을 언급 한 첫 번째 점을 살펴 읽고 나면?
I은 특히 두 가지 표시된 봐하십시오 분기가 CAS 대응 cell.value, 기록 동작, 브랜치 B는 확장을위한 업데이트된다.
ConcurrentHashMap의가, 확장 및 쓰기 작업이 엄격하게 확장과 동시 쓰기를 표시하지 않는 세그먼트 잠금 관할에 처리됩니다 작업 1.6 및 1.7의 확장 넣어 내부에서 실행 자체가 추가됩니다 넣어 잠금 장치, 확장시 같은 세그먼트에 따라서 차단 쓰기 작업이 수행 1.8 확장에 넣어 / 제거 방법은 다른 스레드 확장을 실행하는 경우, 확장을 도와 갈 것입니다 충족, 확장을 시도 잠금 후에 완료됩니다 실제 기록 동작을 수행한다.
는 B 지점이 "고정"되지만 작업이 cellsBusy과 아무 상관이 없지만, "잠금"A 작업의 구현을 금지하지 않습니다. AB 두 가지 상호 배타적이 아니며, 따라서, 동시에 실행할 경우 기록 동작 Striped64 지점 A 및 지점 B의 팽창 동작이있을 것이다.
질문은 그래서 : 왜 이렇게 동시 실행이 문제가되지 않습니다합니까?
동작을 좀 더 자세히 살펴보면, 이해할 것이다. 배열을 변경하지 않고, CAS 세포 개체를 사용하여 속성 업데이트 동작에 대한 참조는 확장 동작이 셀을 복사 한 후 원래대로 셀 어레이 오브젝트 레퍼런스 복사 또는 참조에 의해 유지되어, 세포 개체를 보유 객체입니다.
예를 들어, 오래된, 이전 [1] = 셀라 호출 이전 셀 어레이이며 , cellA.value = 1, 새로운 신규하고있다 새로운 [1] = 셀라라는 새로운 배열의 전개 후. 실제로 실행되는 지점 이전과 동일한 그룹 셀에 의해 유지되며, 어레이의 팽창 후에, 실제로 상관없이 지점 A와 동시 실행이 새로운 어레이가 실행 셀 A 지점의 변화를 볼 수있는 방법 B의 완료 cellA.value = 2 객체입니다.
대신 긴 변수를 직접 셀 객체를 대체하지 왜 당신이 알고있는이 시간. 복사 할 때 긴 [] 완전히 직렬 복사 변경 실행 후 동일한 위치로 이전 배열을 보이지 않으며, 브랜치 B의 확대 한 후, 이전 배열에 직접 분기를 2 개 개의 배열을 분리한다. 예, 오래 들어 [1] = 10, 분기 [5] 이전하고 A 분기 실행 완료가 브랜치 B는 새로운 새로운 배열을 생성 할 때 이전 [1]이 지점 (B)에 복사 된 11로 갱신된다 취할 [1] 여전히 (많은 어쨌든,없는 기록 동작 가지을 어떻게했는지에 상관없이) 10 일 수 있으며,이 작업의 지점이 손실 된 프로그램은 문제가있을 것입니다.
간단한이 개략도가 그린되어 다음, 당신은 볼 수 있습니다.
三、LongAdder
看完了Striped64的讲解,这部分就很简单了,只是一些简单的封装。
public class LongAdder extends Striped64 implements Serializable {
// 构造方法,什么也不做,直接使用默认值,base = 0, cells = null
public LongAdder() {
}
// add方法,根据父类的longAccumulate方法的要求,这里要进行一次CAS操作
// (虽然这里有两个CAS,但是第一个CAS成功了就不会执行第二个,要执行第二个,第一个就被“短路”了不会被执行)
// 在线程竞争不激烈时,这样做更快
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);
}
}
public void increment() {
add(1L);
}
public void decrement() {
add(-1L);
}
// 返回累加的和,也就是“当前时刻”的计数值
// 此返回值可能不是绝对准确的,因为调用这个方法时还有其他线程可能正在进行计数累加,
// 方法的返回时刻和调用时刻不是同一个点,在有并发的情况下,这个值只是近似准确的计数值
// 高并发时,除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值,但是全局加锁在高并发情况下是下下策
// 在很多的并发场景中,计数操作并不是核心,这种情况下允许计数器的值出现一点偏差,此时可以使用LongAdder
// 在必须依赖准确计数值的场景中,应该自己处理而不是使用通用的类
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;
}
// 重置计数器,只应该在明确没有并发的情况下调用,可以用来避免重新new一个LongAdder
public void reset() {
Cell[] as = cells; Cell a;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
a.value = 0L;
}
}
}
// 相当于sum()后再调用reset()
public long sumThenReset() {
Cell[] as = cells; Cell a;
long sum = base;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
// 其他的不说了
}
简单总结下:
这个类是jdk1.8新增的类,目的是为了提供一个通用的,更高效的用于并发场景的计数器。可以网上搜下一些关于LongAdder的性能测试,有很多现成的,我自己就不写了。
jdk1.8的ConcurrentHashMap中,没有再使用Segment,使用了一个简单的仿造LongAdder实现的计数器,这样能够保证计数效率不低于使用Segment的效率。