volatile이란?
자바의 volatile 키워드 또는 코틀린의 @Volatile 애노테이션을 변수 선언시 지정할 수 있다. 사전적 의미로는 ‘휘발성의’라는 뜻을 가지며, 변수 선언시 volatile을 지정하면 값을 메인 메모리에만 적재하게 된다.
volatile 변수를 사용하지 않는 일반적인 경우는 내부적으로 성능 향상을 위해 메인 메모리로부터 읽어온 값을 CPU 캐시에 저장한다. 하지만 멀티쓰레드 애플리케이션에서는 각 쓰레드를 통해 CPU에 캐싱한 값이 상이할 수 있다 (CPU 캐시1 값 ≠ CPU 캐시2 값). 예제코드를 살펴보자
메인 메모리와 CPU캐시의 값이 다른 경우
다음과 같은 Thread를 확장한 서브 클래스가 있다고 가정하자.
class Worker : Thread() {
var stop = false
override fun run() {
super.run()
while(!stop){ }
}
}
이 Worker 클래스는 단순히 무한루프에 빠지는 쓰레드다. 하지만 stop이 true가 되는 순간 루프에서 빠져나와 작업을 마칠 수 있게되고, 쓰레드는 종료된다.
이제 Worker 클래스를 사용한 다음 예제코드를 살펴보자.
@Test
fun `Worker 쓰레드 테스트`(){
repeat(3){
val worker = Worker() // worker 쓰레드 생성
worker.start() // worker 쓰레드 시작
Thread.sleep(100) // 메인 쓰레드 잠시 수면
println("stop을 true로 변경")
worker.stop = true // worker쓰레드의 stop 플래그 변경
worker.join() // worker 쓰레드가 끝날 때까지 메인쓰레드에서 대기
}
println("작업 종료")
}
이 코드를 실행했을 때 직관적으로 생각할 수 있는 출력 메시지는 아마 다음과 같을 것이다.
stop을 true로 변경
stop을 true로 변경
stop을 true로 변경
작업 종료
하지만 현실은 그렇지 않다. “stop을 true로 변경” 메시지만 남긴 채 프로그램이 멈추게 된다.
그 이유는 Worker 쓰레드가 ‘stop’이란 값을 계속 참조해야 하기 때문에 성능을 위해 CPU 캐시에 담아두게 되는데, 이때 메인쓰레드에서 접근하는 worker.stop의 값은 메인 메모리로부터 참조하는 값이므로 서로 다른 두개의 영역에 값이 존재한다. 메인스레드에서 stop을 true로 바꿔도 Worker 쓰레드가 참조하는 stop은 CPU 캐시 영역에 저장된 값이므로 여전히 stop은 false이다. 그렇기 때문에 Worker 쓰레드는 루프를 빠져나오지 못하고 프로그램은 멈추게 된다.
이제 코드를 약간 수정해보자. 수정은 매우 간단하다. @Volatile 만 붙이면 된다.
class Worker : Thread() {
@Volatile
var stop = false
override fun run() {
super.run()
while(!stop){ }
}
}
이제 아까 테스트 코드를 다시 수행하면 기대한 결과를 얻을 수 있다.
결론
@Volatile을 붙이면 변수의 값이 메인 메모리에만 저장되며, 멀티 쓰레드 환경에서 메인 메모리의 값을 참조하므로 변수 값 불일치 문제를 해결할 수 있게된다. 다만 CPU캐시를 참조하는 것보다 메인메모리를 참조하는 것이 더 느리므로, 성능은 떨어질 수 밖에 없다.
참조 링크
http://tutorials.jenkov.com/java-concurrency/volatile.html
6개의 댓글
김정원 · 2022년 3월 17일 10:38 오전
Volatile에 대해 잘 몰랐는데 쉽고 명확하게 알고 갑니다!
감사합니다
Charlezz · 2022년 3월 22일 6:13 오후
ㅎㅎㅎ 감사합니다
오원석 · 2022년 6월 13일 4:37 오후
항상 좋은 글 감사합니다 ^^ 찰스님 글만 보면 쉽게 이해하는 것 같아요!
Charlezz · 2022년 6월 14일 2:13 오후
이해하시는데 도움이 되었다니 다행입니다! 읽어주셔서 감사합니다.
이선주 · 2023년 1월 8일 11:38 오전
안녕하세요, volatile 에 대해 검색하다가 이 글을 읽게 되었습니다.
궁금한 점이 있는데요, 단일 CPU 시스템에서도 이와 같은 문제가 발생하나요?
단일 CPU 시스템에서는 CPU 캐시가 하나라서 멀티스레드 어플리케이션을 실행시키더라도 CPU 캐시 값이 상이해지지 않을 것으로 생각되어 댓글 남깁니다. 감사합니다.
Charlezz · 2023년 2월 9일 8:09 오후
제가 알고있기로는 JVM 구현은 하드웨어에 독립적입니다. 그렇기 때문에 실제 물리적 CPU의 갯수와는 관계 없는 것으로 알고 있습니다. (CPU가 단일 코어라고 한들 듀얼코어로 에뮬레이션 하는 것이 가능합니다)