
CPU는 매우 빠르게 연산을 수행하지만, 메모리 속도는 상대적으로 느립니다.
이 문제를 해결하기 위해 캐시 메모리(Cache Memory)가 사용됩니다.
main 스레드 -> CPU 코어1 -> 캐시 메모리 -> 메인 메모리
work 스레드 -> CPU 코어2 -> 캐시 메모리 -> 메인 메모리
volatile boolean runFlag = true;
Thread mainThread = new Thread(() -> {
runFlag = false;
});
Thread workThread = new Thread(() -> {
while (runFlag) {
// do something...
}
});
위 코드에서 mainThread가 runFlag 값을 false로 변경해도,
workThread에서는 여전히 true로 보일 수 있습니다. 왜 그럴까요?
각 스레드는 CPU 코어에서 실행되며, 코어는 캐시 메모리를 활용합니다.
runFlag 값이 캐시 메모리에 저장되면, 각 코어는 자신의 캐시 값을 계속 사용하게 됩니다.
즉, mainThread가 runFlag = false;로 변경해도, workThread가 실행되는 CPU 코어의 캐시 메모리에는 반영되지 않을 수 있습니다.
이 부분은 CPU 설계 및 캐시 동기화 정책에 따라 다릅니다. 즉, 명확한 시점을 보장할 수 없습니다.
이런 메모리 가시성 문제를 해결하지 않으면, 멀티 스레드 프로그래밍에서 의도치 않은 동작이 발생할 수 있습니다.
volatile 키워드 사용volatile boolean runFlag = true;
volatile을 사용하면 모든 스레드가 항상 메인 메모리 값을 읽고 씁니다.synchronized 블록 사용synchronized (this) {
runFlag = false;
}
synchronized를 사용하면 스레드 간 동기화가 보장됩니다.Lock 사용Lock lock = new ReentrantLock();
lock.lock();
try {
runFlag = false;
} finally {
lock.unlock();
}
Lock을 사용하면 synchronized보다 유연한 동기화가 가능합니다.volatile, synchronized, Lock 등을 활용해야 한다.이제 멀티 스레드 프로그래밍을 할 때 메모리 가시성을 꼭 고려 하시길 바랍니다.!