public static void main(String[] args) {
MyTask task = new MyTask();
Thread t = new Thread(task, "work");
log("runFlag = " + task.runFlag);
t.start();
sleep(1000);
log("runFlag를 false로 변경 시도");
task.runFlag = false;
log("runFlag = " + task.runFlag);
log("main 종료");
}
static class MyTask implements Runnable {
boolean runFlag = true;
//volatile boolean runFlag = true;
@Override
public void run() {
log("task 시작");
while (runFlag) {
// runFlag가 false로 변하면 탈출 }
log("task 종료");
}
}
runFlag
를 사용해서 스레드의 작업을 종료한다.runFlag
값이 false
가 되면 무한 루프를 탈출하며 작업을 종료한다.main
스레드가 runFlag
의 값을 false
로 변경한다.runFlag
의 값이 false
가 되었으므로 work
스레드는 무한루프를 탈출하며 작업을 종료한다.work
스레드가 실행하는 while
조건은 false
가 되었지만 while
문을 빠져나오지 못했다.runFlag
의 값을 사용하면 cpu는 이 값을 효율적으로 처리하기 위해 먼저 runFlag
를 캐시 메모리에 불러온다. 이후에는 캐시 메모리에 있는 runFlag
를 사용하게 된다.main
스레드의 runFlag
를 false
로 설정한다. 자연스럽게 main
스레드를 돌리는 cpu 코어의 캐시 메모리의 runFlag
값이 false
가 된다.work
스레드가 사용하는 cpu 코어의 캐시 메모리는 여전히 값이 true
이다.false
가 될지는 알 수 없다.work
스레드가 사용하는 캐시메모리에 다시 불러와야 한다.캐시 메모리를 사용하면 cpu 처리 성능을 개선할 수 있다. 하지만 여러 스레드에서 같은 시점에 정확히 같은 데이터를 보는 것이 중요할 수도 있다.
이를 해결하는 방안은 아주 간단하다. 성능을 포기하는 대신 값을 읽을 때 값을 쓸때
모두 메인 메모리에 직접 접근하면 된다.
자바에서는 vloatile
이라는 키워드로 이런 기능을 제공한다.
volatile boolean runFlag = true;
runFlag
에 대해서는 캐시메모리를 사용하지 않고, 값을 읽거나 쓸 때 항상 메인 메모리에 직접 접근한다.runFlag
를 false
로 변경하자마자 task 종료
가 출력되는 것을 알 수 있다.메모리 가시성
멀티스레드 환경에ㅐ서 한 스레드가 변경한 값이 다른 스레드에서 언제 보이는지에 대한 것을 메모리 가시성이라한다. 이름 그대로 메모리에 변경한 값이 보이는 가 아닌가의 문제이다.
happens-before
happens-before 관계는 자바 메모리 모델에서 스레드간의 작업 순서를 정의하는 개념이다. 만약 A 작업이 B 작업보다 happend-before 관계에 있다면 A 작업에서의 모든 메모리 변경 사항은 B 작업에서 볼 수 있다. 즉 A 작업에서 변경된 내용은 B 작업이 시작되기 전에 모두 메모리에 반영된다.
volatile 규칙
한 스레드에서 volatile
변수에 대한 쓰기 작업은 해당 변수를 읽는 모든 스레드에 보이도록 한다. 즉 volatiile
변수에 대한 쓰기 작업은 그 변수를 읽는 작업보다 happens-before
관계를 형성한다.