티스토리 뷰
volatile이란?
volatile은 자바의 키워드이며, 변수의 타입 앞에 사용된다. 그리고 선언한 변수를 캐시 메모리가 아닌 메인 메모리를 통해 읽는다. 아래 예제를 통해 알아보았다.
volatile을 사용X 예제
Main.java
public class Main {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
Thread thread = new Thread(task, "task");
System.out.println("Task.flag = " + task.flag);
thread.start();
Thread.sleep(1000);
System.out.println("Task.flag를 false로 변경");
task.flag = false;
System.out.println("Task.flag = " + task.flag);
System.out.println("main 종료");
}
static class Task implements Runnable {
boolean flag = true;
@Override
public void run() {
System.out.println("Task 시작");
while(flag) {
}
System.out.println("Task 종료");
}
}
}
실행 결과

위의 실행 결과를 보면 main 스레드가 종료되었는데 task 스레드는 계속 실행 중이며, main 스레드가 task 스레드의 flag를 false로 변경했고 콘솔에서도 false로 나오는데 task 스레드는 while문이 계속 반복되는 것이다. 왜 이런 결과가 나왔는지 아래 그림으로 나타냈다.

위의 그림은 예제를 간단히 표현하였다. 여기서 봐야할게 스레드 내의 캐시 메모리이다. 각 스레드에 캐시 메모리가 존재하는데 각 스레드는 메인 메모리가 아닌 캐시 메모리를 통해 읽는다. 그래서 main 스레드에서 task의 flag를 false로 바꿔도 main의 캐시 메모리만 변경된다. (main 스레드에서 flag 변경 이후 콘솔 출력 코드 실행을 하면 main 스레드의 컨텍스트 스위치가 발생하여 메인 메모리에서 flag 변경이 일어날 수 있다.) ※캐시 메모리란? 메인 메모리와 CPU 간의 데이터 속도 향상을 위한 중간 버퍼 역할을 하는 메모리이다.
task 캐시 메모리의 flag를 변경하는 방법
캐시 메모리를 메인 메모리에 반영하거나, 메인 메모리의 변경 내역을 캐시 메모리에 다시 불러오는 것은 CPU의 환경에 다를 수 있으며, 주로 컨텍스트 스위칭이 발생할 때 일어난다. (환경에 따라 다를 수 있다.)
@Override
public void run() {
System.out.println("Task 시작");
while(flag) {
// 컨텍스트 스위칭 발생
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("Task 종료");
}

위의 코드는 task 스레드의 while문 안에 sleep를 걸어서 컨텍스트 스위칭이 발생하게 하였다. 그리고 실행 결과에서 task가 종료된 것을 확인할 수 있다. (main 스레드에서 flag 변경 이후 콘솔 출력을 하여 컨텍스트 스위칭이 발생하므로 메인 메모리가 변경되었고 그 후 task 스레드에서 컨텍스트 스위칭이 발생했기 때문에 메인 메모리를 읽어서 캐시 메모리의 flag가 변경되었다.)
volatile를 사용하여 해결
맨 처음의 문제를 해결하기 위해 컨텍스트 스위칭이 발생할 수 있는 sleep, 콘솔 출력 등 불필요한 코드를 넣어야 할까? 그리고 코드를 넣어도 CPU 환경 등 여러 환경에 따라 종료 시점과 변수를 읽는 시점이 달라질 수 있다. 그래서 volatile를 사용하면 해결된다.
volatile을 사용O 예제
main.java
public class Main {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
Thread thread = new Thread(task, "task");
System.out.println("Task.flag = " + task.flag);
thread.start();
Thread.sleep(1000);
System.out.println("Task.flag를 false로 변경");
task.flag = false;
System.out.println("Task.flag = " + task.flag);
System.out.println("main 종료");
}
static class Task implements Runnable {
volatile boolean flag = true;
@Override
public void run() {
System.out.println("Task 시작");
while(flag) {
}
System.out.println("Task 종료");
}
}
}
flag 변수 타입 앞에 volatile를 입력하였다.
실행 결과

위의 실행 결과를 보면 정상적으로 종료되었다. 이는 main 스레드와 task 스레드가 캐시 메모리가 아닌 메인 메모리에 직접 접근하기 때문이다. volatile는 캐시 메모리가 아닌 메인 메모리에 접근하도록 한다.

위의 그림은 volatile 코드를 사용할 때의 예제를 표현하였다. 두 개의 스레드는 flag 변수를 메인 메모리를 통해 접근한다. 하지만 중간 캐시 메모리가 아닌 메인 메모리에 접근하기 때문에 속도 측면에서는 떨어질 수밖에 없다.
정리
volatile은 멀티 스레드에서 변수 메모리를 캐시 메모리가 아닌 메인 메모리에 사용함으로써 다른 스레드에서도 일관성 있는 데이터 참조가 가능한 변수를 만들 수 있다. 하지만 중간 캐시 메모리가 아닌 메인 메모리에 접근하기 때문에 속도에서는 떨어진다.
본 포스팅은 “김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성/인프런”를 학습한 내용을 정리한 것
'Java > Java' 카테고리의 다른 글
| <Java> ReentrantLock이란? (0) | 2024.10.26 |
|---|---|
| <Java> sychronized이란? (1) | 2024.10.22 |
| <Java> Thread join() 메서드란? (3) | 2024.10.16 |
| <Java> 불변 객체가 필요한 이유? (2) | 2024.10.13 |
| <Java> 해시 자료 구조에서 equals(), hashCode() 메서드의 중요성 (0) | 2024.07.01 |
