volatile은 변수의 수정 사항이 모든 스레드에서 가시성을 보장하는 변수 수식자입니다. 변수가 volatile로 선언되면, 이 변수의 읽기 및 쓰기는 로컬 캐시 메모리를 우회하여 주 메모리에서 직접 일어납니다. 이는 스레드 내에서 값을 로컬로 캐시하는 것을 방지합니다.
synchronized는 가시성뿐만 아니라 상호 배제를 보장하는 키워드입니다: 한 순간에 하나의 스레드만 특정 객체에 대해 동기화된 블록을 실행할 수 있습니다.
volatile 변수를 사용해야 하는 경우는 단순 플래그와 카운터의 경우입니다. 만약:
volatile 사용 예:
class Flag { private volatile boolean running = true; public void stop() { running = false; } public void loop() { while (running) { // ... } } }
변수의 증분을 원자적으로 보장하기 위해 volatile을 사용할 수 있나요?
답변:
아니요, volatile은 연산의 원자성을 보장하지 않습니다. 예를 들어, counter++는 counter가 volatile로 선언되어 있어도 원자적이지 않습니다. 왜냐하면 이 연산은 읽기, 수정 및 쓰기를 포함하는데(여러 개의 작업), 이 과정이 다른 스레드에 의해 중단될 수 있기 때문입니다. 원자성은 synchronized 또는 java.util.concurrent.atomic 패키지의 클래스를 통해 보장됩니다.
예:
class Counter { private volatile int count = 0; public void increment() { count++; // 원자적이지 않은 작업! } }
이야기
온라인 상점 프로젝트에서 주문 처리 스레드의 종료 플래그가 volatile 없이 구현되었습니다. 그 결과, 한 스레드가 다른 스레드에서 변경된 변수를 보지 못하여 무한 루프에 빠졌습니다. 진단에는 며칠이 걸렸습니다.
이야기
재무 시스템에서 개발자는 작업 카운터로 volatile int를 사용했습니다. 피크 시 로드에서 작업 수가 "소실"되기 시작했습니다. 이유는 증분 시 복잡한 연산에서 원자성 손실입니다.
이야기
개발자들은 volatile과 synchronized를 혼동하여 volatile을 критical section에 대한 상호 배제를 보장하는 데 사용하려 했고, 이는 데이터 레이스와 다중 스레드 애플리케이션에서 찾아내기 어려운 버그를 초래했습니다.