[Java] AtomicInteger
AtomicInteger, java.util.concurrent.atomic.AtomicInteger, 원자 정수, atomic int
정의
java.util.concurrent.atomic.AtomicInteger 는 lock-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 타입
| 타입 | 용도 |
|---|---|
| AtomicInteger | int |
| AtomicLong | long |
| AtomicBoolean | boolean |
| AtomicReference | 임의 객체 참조 |
| AtomicIntegerArray | int 배열의 원자 갱신 |
| AtomicLongArray | long 배열 |
| AtomicReferenceArray | 객체 배열 |
| LongAdder | 고경합 카운터 |
| DoubleAdder, LongAccumulator | 람다 누적 |
참고
- volatile
- ReentrantLock
- AtomicReference
- LongAdder
- Non-Blocking
- Brian Goetz, Java Concurrency in Practice, §15 Atomic Variables and Nonblocking Synchronization
이 글의 용어 (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 은 호출이 결과를 기다리지 않고 즉시 반환 하는 실행 방식. 결과가 준비되지 않았다면 "아직 안 됐다" 라는 신호 ( , , , 등) 를 반환하고, …
💬 댓글