Java volatile과 happens-before: 가시성, 순서성, 원자성의 경계
기준: Java 17+. 이 글은 Java Memory Model을 다루며, 특정 CPU 아키텍처에 의존하지 않습니다.
1. volatile이 보장하는 것
volatile 변수에 대한 쓰기는 이후 다른 스레드가 같은 변수를 읽는 것보다 happens-before에 앞섭니다. 가시성과 어느 정도의 순서성은 제공하지만, 여러 연산을 하나의 원자적 연산으로 바꾸어 주지는 않습니다.
volatile boolean stopped으로 중지 플래그를 안전하게 발행할 수 있습니다. 반면 volatile int count는 count++을 안전하게 처리할 수 없습니다. 증가 연산이 읽기, 1 더하기, 값 다시 쓰기의 세 단계로 이루어지기 때문입니다.
2. 올바른 도구 고르기
단일 변수의 원자적 갱신에는 AtomicInteger나 AtomicReference를, 복합적인 상태 전이에는 락, 동시성 컨테이너 또는 CAS 루프를 사용합니다. 변경 불가능한 설정값을 발행할 때는 volatile 참조를 쓸 수 있고, 하위 수준의 메모리 순서가 정말 필요할 때만 VarHandle을 고려합니다.
3. happens-before의 출처
스레드 시작, 스레드 종료, 락의 해제와 획득, Future 완료, 그리고 동시성 컨테이너에 대해 규정된 연산도 가시성 관계를 형성합니다. JMM 규칙 대신 “명령어 재배열 금지”를 대용으로 삼지 마세요.
4. 검증
복잡한 메모리 순서는 jcstress로, 성능 비교는 JMH로, 업무 상태 쓰기는 경쟁 상황과 실패 주입을 포함한 동시성 테스트로 확인합니다. volatile은 락이 아니며, 복합 연산의 원자성을 보장하지도 않습니다.