본문으로 건너뛰기
김신건의 로그

[Java] AtomicInteger

· 수정 · 📖 약 1분 · 427자/단어 #java #concurrent #atomic #cas #lock-free
AtomicInteger, java.util.concurrent.atomic.AtomicInteger, 원자 정수, atomic int

정의

java.util.concurrent.atomic.AtomicIntegerlock-free 로 원자성을 보장 하는 int 래퍼. 내부적으로 CAS (Compare-And-Swap) 를 사용해 락 없이 atomic 한 read-modify-write 를 수행.

volatile int i; i++; 가 thread-safe 가 아닌 이유 (read + increment + write 세 단계) 를 해결한다.

시각화

핵심 메서드

AtomicInteger a = new AtomicInteger(0);

a.get();                          // 현재 값
a.set(10);                        // 설정
a.incrementAndGet();              // ++a, atomic
a.getAndIncrement();              // a++, atomic
a.decrementAndGet();              // --a
a.addAndGet(5);                   // a += 5
a.getAndAdd(5);                   // 더하기 전 값 반환

a.compareAndSet(10, 99);          // 현재 값이 10 이면 99 로, 성공하면 true
a.updateAndGet(x -> x * 2);       // Java 8+, 람다로 갱신
a.accumulateAndGet(3, Integer::sum);

CAS, 그 원리

public final int incrementAndGet() {
    while (true) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))  // current 와 일치하면 next 로
            return next;
        // 일치하지 않으면 다른 스레드가 바꾼 것, 다시 시도
    }
}

CPU 명령어 (LOCK CMPXCHG on x86) 한 번으로 “값을 비교하고 일치하면 바꾸기” 를 atomic 하게 수행. 락 acquire/release 비용보다 훨씬 작다.

volatile vs synchronized vs AtomicInteger

옵션가시성atomic read-modify-write락 비용
int i;없음
volatile int i;✗ (i++ 안 됨)없음
synchronized중간
AtomicInteger✓ (CAS)매우 작음 (저경합)

IMPORTANT

단일 변수의 atomic 갱신만 필요하면 AtomicInteger 가 거의 항상 최선. 여러 변수 간 invariant 보호가 필요하면 ReentrantLock 또는 synchronized.

고경합에서의 문제, LongAdder

CAS 는 저경합에서 빠르다. 하지만 수십 스레드가 같은 AtomicInteger 에 동시 증가 하면 CAS 실패가 폭주, 성능이 락보다 떨어질 수 있다.

이때 LongAdder 가 해법. 내부적으로 여러 cell 에 분산 누적 후 합산.

사용 예

카운터

class RequestCounter {
    private final AtomicInteger total = new AtomicInteger();

    public void onRequest() { total.incrementAndGet(); }
    public int count() { return total.get(); }
}

시퀀스 생성

private final AtomicInteger seq = new AtomicInteger();
public int nextId() { return seq.incrementAndGet(); }

상태 전이 (CAS)

private final AtomicInteger state = new AtomicInteger(STATE_READY);

public boolean tryStart() {
    return state.compareAndSet(STATE_READY, STATE_RUNNING);
}

상태 머신을 락 없이 안전하게 전이.

ABA 문제

CAS 의 알려진 한계. A → B → A 로 변한 값을 “안 바뀜” 으로 잘못 판단할 수 있다.

// 1. T1 이 값 A 를 읽음
int v = a.get();
// 2. T2 가 A → B 로 변경
// 3. T2 가 B → A 로 변경
// 4. T1 의 CAS(A, x) 성공, 하지만 이 사이에 두 번 바뀐 사실 모름

해법: AtomicStampedReference 가 stamp (버전) 도 같이 비교.

다른 Atomic 타입

타입용도
AtomicIntegerint
AtomicLonglong
AtomicBooleanboolean
AtomicReference임의 객체 참조
AtomicIntegerArrayint 배열의 원자 갱신
AtomicLongArraylong 배열
AtomicReferenceArray객체 배열
LongAdder고경합 카운터
DoubleAdder, LongAccumulator람다 누적

참고

이 글의 용어 (5개)
[Java] AtomicReferencejava
정의 는 객체 참조를 atomic 하게 갱신 하는 래퍼. 참조 + CAS 의 결합. 의 객체 버전. 불변 객체 패턴, 상태 머신, lock-free 자료구조의 빌딩 블록. 핵심 …
[Java] LongAdderjava
정의 는 고경합 카운터 를 위한 자료구조. JDK 1.8 도입. 보다 동시 증가 throughput 이 압도적으로 높다. 원리는 striped counter (분산 누적). 여러…
[Java] ReentrantLockjava
정의 는 키워드와 같은 상호 배제 (mutual exclusion) 를 제공하는 클래스 기반 락. JSR-166 (Java 5) 에서 추가됐다. "재진입 (reentrant)" …
[Java] volatilejava
정의 은 Java 의 키워드. 필드에 붙이면 두 가지를 보장한다. 1. 가시성 (visibility): 한 스레드의 쓰기가 다른 모든 스레드에 즉시 보인다. CPU 캐시에 머무르…
Non-Blocking (논블로킹)concurrency
정의 Non-Blocking 은 호출이 결과를 기다리지 않고 즉시 반환 하는 실행 방식. 결과가 준비되지 않았다면 "아직 안 됐다" 라는 신호 ( , , , 등) 를 반환하고, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기