volatile
volatile 기능
가시성 보장 (visibility)
기본적으로, Java 스레드는 각자의 CPU 캐시에 변수를 저장하여 접근 속도를 높입니다. 그러나 다른 스레드가 이 변수를 변경해도 캐시된 값이 갱신되지 않을 수 있습니다.
volatile키워드를 사용하면 변수의 값을 메인 메모리에서 직접 읽고 쓰게 되어, 모든 스레드가 항상 최신 값을 볼 수 있습니다.
가시성?
volatile을 지정하지 않으면 쓰레드는 RAM이 아닌 로컬 캐시를 우선적으로 확인하여 사용한다.
실행 결과
SharedData의 counter를 volatile 지정 안했을때
SharedData의 counter를 volatile 지정 했을때
변수의 값 저장 및 읽기 과정
변수의 초기화:
변수가 처음 선언되고 초기화되면, 그 값은 메인 메모리에 저장됩니다.
자주 접근하는 데이터는 캐시에 저장되어 CPU가 빠르게 접근할 수 있도록 합니다.
값 읽기:
CPU는 먼저 레지스터에서 값을 찾고, 없으면 캐시를 탐색합니다.
캐시에 값이 있으면 (캐시 히트), CPU는 캐시에서 값을 읽습니다.
캐시에 값이 없으면 (캐시 미스), 메인 메모리에서 값을 읽어 캐시에 저장한 후 CPU가 읽습니다.
값 쓰기:
CPU는 값을 레지스터에 저장하고, 캐시에 업데이트하며, 나중에 메인 메모리에 씁니다 (write-back 방식).
쓰기 지연으로 인해 더 빠른 쓰기 성능을 제공할 수 있으나, 쓰기가 메인 메모리로 이동할 때까지 데이터의 일관성을 보장할 수 없습니다.
때때로 즉시 메인 메모리에 쓰는 경우도 있습니다 (write-through 방식).
메인 메모리에 있는 최신 값으로 유지됩니다.
쓰기 연산이 느리게 진행될 수 있지만, 일관성과 신뢰성을 제공합니다.
한계
원자성 보장 불가
volatile 변수는 단일 연산으로 처리되지 않습니다. 즉, 변수의 값을 읽고 쓰는 작업은 원자적으로 이루어지지 않습니다.
상태 동기화 보장 불가
volatile 변수는 단순히 메모리 가시성만 보장합니다. 쓰레드 사이의 상태 동기화를 보장하지 않습니다.
메모리 사용량이 큼
volatile 변수는 사용량이 큰 메모리 공간을 차지하므로, 많은 수의 volatile 변수를 사용하면 메모리 사용량이 크게 증가할 수 있습니다.
캐시 일관성 미보장
volatile 변수는 캐시 일관성을 보장하지 않습니다. 따라서 변수의 값을 읽거나 쓸 때 캐시와 메인 메모리 간의 일관성을 유지해야 합니다.
순서 보장 불가능
volatile 변수는 변수 간의 순서를 보장하지 않습니다. 따라서 변수 간의 순서가 중요한 경우에는 volatile만으로는 부족할 수 있습니다.
비효율적인 성능
volatile 변수는 쓰레드 간의 메모리 가시성을 보장하기 위해 메모리에 접근하는 오버헤드가 있을 수 있습니다. 이는 성능에 영향을 줄 수 있습니다.
(예시 코드) 원자성 보장 불가
아래 예시 코드는 은행 잔고가 A에서 B로 이체시 은행에 있는 총 금액은 3000이어야 하지만 맞지 않아 에러가 발생하는 원자성이 보장되지 않는 예시입니다.
(예시 코드) 복잡한 복수의 상태 변환 관리 불가
아래는 공유 객체 SharedData 두 변수가 항상 특정 조건을 만족하도록 일관된 상태를 유지해야 하는 예제입니다. update 메서드는 a와 b를 동일한 값으로 업데이트합니다. 그러나 volatile은 두 변수 간의 일관성을 보장하지 않습니다. 따라서 스레드 t2가 isConsistent 메서드를 호출할 때 a와 b가 일치하지 않는 상태를 발견할 수 있습니다.
그럼 volatile을 사용하지 않는게 맞을까?
일반적으로 말하자면, volatile은 synchronized에 비해 더 간단한 상황에서 주로 사용됩니다. 가장 일반적으로는 쓰레드 간에 변수의 변경을 즉시 반영해야 하는 경우에 volatile을 사용합니다. 예를 들어, 상태 플래그나 이벤트 플래그를 설정하고 읽는 경우가 이에 해당합니다. 하지만, 복잡한 상황에서는 synchronized를 사용해야 하는 경우가 많습니다. 예를 들어 여러 개의 변수를 함께 수정하고 이에 따른 불변식을 보장해야 하는 경우, synchronized 블록이나 메소드를 사용하여 상태 동기화를 보장해야 합니다. 결국, 사용하는 상황에 따라 적절한 도구를 선택해야 합니다. 가시성만을 보장하고자 할 때는 volatile을 사용할 수 있지만, 상태 동기화가 필요할 경우에는 synchronized를 사용해야 합니다. 또한, 최신 버전의 자바에서는 java.util.concurrent 패키지의 동시성 유틸리티 클래스들을 사용하는 것도 고려할 가치가 있습니다.
Last updated