학습의 jdk1.8 LongAdder 소스

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的效率。
 

发布了19 篇原创文章 · 获赞 149 · 访问量 80万+

추천

출처blog.csdn.net/truelove12358/article/details/100552107