Java에서 동시성 프로그래밍을 하다 보면 CAS(Compare-And-Swap)라는 개념을 자주 접하게 된다.
그러나 CAS가 항상 안전한 것은 아니다. 그중 대표적인 함정이 바로 ABA 문제이다.
🔍 ABA 문제란?
ABA 문제는 값이 A였다가 B로 바뀌었다가 다시 A로 돌아왔을 때 발생한다.
CAS는 값이 같으면 문제없이 통과시키는 구조이기 때문에
중간에 값이 변경되었다는 사실을 인식하지 못한다.
즉, 값은 같지만 의미상으로는 완전히 다른 데이터일 수 있음에도 이를 구분하지 못하는 것이 ABA 문제의 핵심이다.
🧪 왜 위험할까?
CAS는 다음과 같은 방식으로 작동한다:
if (value == expected) {
value = newValue;
}
이때, 다른 스레드가
- A → B → A로 값을 변경해버리면,
- CAS는 여전히 A == expected라고 판단하여
- 정상적으로 변경된 것처럼 보이지만, 사실은 의도하지 않은 데이터로 교체가 이루어진다.
이와 같이 이전 상태로 되돌린 값이 본래의 의미와 다를 수 있음에도 불구하고, 이를 감지하지 못하는 점이 ABA 문제의 핵심 위험 요소이다.
⚠️ 어떤 상황에서 문제가 발생할까?
- 스택/큐와 같이 노드의 연결 상태를 직접 관리하는 자료구조
- Lock-free 구조를 사용하는 알고리즘
- 참조 기반으로 동작하는 데이터 구조에서 주로 발생한다
예를 들어, 다음과 같은 시나리오가 있다:
AtomicReference<Node> top = ...
// 스레드 A: top == NodeA 를 읽음
// 스레드 B: NodeA 제거 → NodeB 삽입 → 다시 NodeA 삽입
// 스레드 A: 여전히 top == NodeA 이므로 CAS 성공 → 문제 발생
이 경우 NodeA는 같은 인스턴스처럼 보이지만,
연결된 내부 상태가 완전히 바뀌었기 때문에 CAS로 처리하면 의도치 않은 결과가 발생한다.
🛡 어떻게 해결할까?
✅ 버전 정보를 함께 비교하기 – AtomicStampedReference
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
- 값(value)과 함께 버전(stamp)을 관리하여
- 값뿐 아니라 변경 이력까지 비교 대상에 포함함으로써 ABA 문제를 방지할 수 있다.
✅ 의미 상태 추적 – AtomicMarkableReference
- 값에 더해 Boolean 플래그를 함께 저장하여
- 특정 의미 변화 여부를 함께 판단하는 방식이다.
✅ 실무에서 반드시 알고 있어야 할 포인트
- Java의 기본 AtomicInteger, AtomicReference는 ABA 문제에 취약하다.
- ConcurrentLinkedQueue, LinkedTransferQueue와 같은 고급 동시성 컬렉션 구현체는
내부적으로 AtomicStampedReference 혹은 버전 관리를 통해 이 문제를 제어하고 있다.
✍️ 마무리 요약
CAS는 빠르고 효율적인 동기화 수단이지만,
‘값이 같다고 해서 진짜 같은 것인가?’를 항상 의심해야 한다.
ABA 문제는 그 의심이 필요한 대표적인 사례이다.
'언어 > Java' 카테고리의 다른 글
왜 불변 객체를 써야 할까? (0) | 2025.05.03 |
---|---|
happens-before란 무엇인가? (0) | 2025.05.02 |
Executor 프레임워크2 - Executor 프레임워크가 등장한 이유 (0) | 2025.04.30 |
Executor 프레임워크1 - 스레드를 직접 사용할 때 문제점 (0) | 2025.04.30 |
synchronized 키워드 이해도 체크 (0) | 2025.04.28 |