[Java] LongAdder
LongAdder, java.util.concurrent.atomic.LongAdder, DoubleAdder, 스트라이프 카운터
정의
java.util.concurrent.atomic.LongAdder 는 고경합 카운터 를 위한 자료구조. JDK 1.8 도입. AtomicLong 보다 동시 증가 throughput 이 압도적으로 높다.
원리는 striped counter (분산 누적). 여러 cell 에 나누어 누적하고, 합산할 때 모두 더한다. CAS 충돌이 발생하면 다른 cell 을 시도 → 거의 모든 increment 가 무경합.
시각화
동작
LongAdder count = new LongAdder();
count.increment(); // ++count
count.add(5);
count.sum(); // 모든 cell 합산, 추정값
count.longValue(); // == sum()
count.reset();
AtomicLong.incrementAndGet() 처럼 새 값을 반환하지는 않는다. 누적 결과만 보장.
왜 AtomicLong 보다 빠른가
AtomicInteger / AtomicLong 의 CAS 루프는 다음과 같다.
스레드 N 개가 동시에 increment:
T1: CAS(0, 1) 성공
T2: CAS(0, 1) 실패 → 재시도 → CAS(1, 2) 성공
T3: CAS(0, 1) 실패 → 재시도 → CAS(1, 2) 실패 → 재시도 → CAS(2, 3) 성공
...
경합이 심하면 거의 모든 스레드가 여러 번 CAS 실패. 성능이 락보다 떨어질 수 있다.
LongAdder 는.
스레드 N 개가 동시에 increment:
T1 → cell[hash(T1) % cells.length].add(1)
T2 → cell[hash(T2) % cells.length].add(1)
T3 → cell[hash(T3) % cells.length].add(1)
... (각자 다른 cell 에 누적, 충돌 거의 없음)
cells 배열의 크기가 동적으로 늘어나면서 경합이 분산된다.
sum 의 약점
long total = adder.sum(); // 모든 cell 의 합
합산 시점에 다른 스레드가 add 중이면 그 값은 반영 안 될 수 있다. “순간 정확한 카운트” 는 보장 안 됨.
워크로드 통계, request count 같이 eventual consistency 가 허용되는 카운터에 적합.
사용 예
request 카운터
class Metrics {
private final LongAdder requests = new LongAdder();
private final LongAdder errors = new LongAdder();
public void onRequest() { requests.increment(); }
public void onError() { errors.increment(); }
public long requestCount() { return requests.sum(); }
public long errorCount() { return errors.sum(); }
}
Map 의 값으로
ConcurrentMap<String, LongAdder> counts = new ConcurrentHashMap<>();
counts.computeIfAbsent(key, k -> new LongAdder()).increment();
이 패턴이 흔한 idiom. ConcurrentHashMap + LongAdder 조합으로 키별 카운트.
DoubleAdder, LongAccumulator, DoubleAccumulator
DoubleAdder: 같은 패턴의 double 버전LongAccumulator: 일반화, 임의의 람다(prev, x) -> next로 누적DoubleAccumulator: double 버전
LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);
max.accumulate(42);
max.accumulate(100);
max.get(); // 100
AtomicLong vs LongAdder 의 선택
| 시나리오 | 권장 |
|---|---|
| 단일 스레드 / 저경합 | AtomicInteger / AtomicLong |
| 고경합 + sum 만 필요 | LongAdder |
| 고경합 + 매 갱신마다 결과 필요 | AtomicLong (CAS 비용 감수) |
| custom accumulation | LongAccumulator |
참고
이 글의 용어 (3개)
- [Java] AtomicIntegerjava
- 정의 는 lock-free 로 원자성을 보장 하는 래퍼. 내부적으로 CAS (Compare-And-Swap) 를 사용해 락 없이 atomic 한 read-modify-write …
- [Java] ConcurrentHashMapjava
- 정의 는 고동시성 환경에서 사용 가능한 구현. JSR-166 (Java 5) 도입, Java 8 에서 내부 구조가 크게 재작성됐다. 과 같은 인터페이스를 제공하면서 thread-…
- Non-Blocking (논블로킹)concurrency
- 정의 Non-Blocking 은 호출이 결과를 기다리지 않고 즉시 반환 하는 실행 방식. 결과가 준비되지 않았다면 "아직 안 됐다" 라는 신호 ( , , , 등) 를 반환하고, …
💬 댓글