AI 요약
멀티스레드에서 변수 접근을 다루며 volatile, synchronized, AtomicInteger가 각각 무엇을 보장하고 어떤 한계가 있는지를 짚습니다. volatile은 가시성·순서만, synchronized는 상호 배제로 복합 연산을 보호하고, AtomicInteger는 CAS로 원자적 증감만 제공하니 문제의 성격(가시성인지 원자성인지)을 먼저 가려야 합니다.
멀티스레드 환경에서 이런 코드를 작성했다고 해보자.
01private volatile int count = 0;02 03public void increase() {04 count++;05}volatile를 붙였으니 안전할 것 같지만, 그렇지 않다.
count++는 하나의 연산처럼 보이지만 실제로는 대략 다음과 같은 과정을 거친다.
두 쓰레드가 동시에 count = 0 을 읽으면 둘 다 1을 저장할 수 있다.
volatile이 보장하는 건 원자성이 아니라 가시성과 메모리 순서다.
01- 가시성 보장02- 명령어 재배치 제한03- 복합 연산의 원자성은 보장 X이런 증가 연산을 안전하게 만들려면 synchronized 를 사용할 수 있다.
01private int count = 0;02 03public synchronized void increase() {04 count++;05}한 번에 한 스레드만 실행하도록 만들어 count++ 전체를 지킨다.
단순한 증가라면 AtomicInteger 를 사용할 수 있다.
01private final AtomicInteger count = new AtomicInteger();02 03public void increase() {04 count.incrementAndGet();05}AtomicInteger 는 내부적으로 CAS를 활용해 원자적인 변경을 제공한다.
CAS란 락을 사용하지 않고 원자적인 연산을 수행할 수 있는 방법이다.
| 항목 | ||
|---|---|---|
| volatile | 가시성 + 순서 | 원자성 X |
| synchronized | 상호 배제 + 가시성 | 복합 로직 보호 O |
| AtomicInteger | CAS 기반 원자 연산 | 단순 카운터 등에 적합 |
결국 중요한 건 셋 중에 뭐가 더 좋은지가 아니라, 내가 해결해야 하는 문제가 가시성인지 원자성인지 먼저 구분하는 것이다.