자바 volatile 키워드에 대해 알아보기 전에 앞서, 관련된 내용인 컴퓨터 구조와 관련된 내용에 대해 간단하게 설명하려 합니다.
Memory-wall Problem
Memory-wall 문제는 컴퓨터 구조에서 CPU(프로세서)의 속도가 메모리 시스템과 데이터를 주고받는 속도를 능가할 때 발생하는 현상을 말합니다. 그로인해 프로세서는 빠른 연산 처리속도를 가짐에도 불구하고 메모리에서 CPU로 데이터를 가져올 때까지 기다려야 하므로 성능이 느려지고 속도도 제한되게 됩니다.
따라서 위 그림과 같이 CPU(그림에서의 Core 영역)는 CPU cache를 둠으로써, CPU와 메모리 사이의 데이터 전송 속도 문제를 해결했습니다.
하지만 이로 인해, CPU cache의 데이터와 RAM사이의 데이터가 일치하지 않는 문제가 발생하게 되었습니다.
캐시 일관성 문제(Cache Coherence Challenges)
자바 병렬 프로그래밍이라는 책에서 한 예시를 가져왔습니다.
public class TaskRunner {
private static int number;
private static boolean ready;
private static class Reader extends Thread {
@Override public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
}
public static void main(String[] args) {
new Reader().start();
number = 42;
ready = true;
}
}
TaskRuner 클래스는, number와 flag 변수인 ready를 가집니다. 그리고 main 메서드에서 새로운 쓰레드를 만들고, 새로운 쓰레드에서 run 메서드를 실행합니다.
run 메서드는 ready값이 true면 number 값을 출력하고 false라면 yield()를 통해 쓰레드를 대기상태로 만드는 간단한 메서드 입니다.
단순히 코드로 보았을때, Reader의 run 메서드가 실행된 후, 잠시 후 42가 출력될 것으로 예상할 수 있습니다. 하지만 잠시가 아닌 무한루프에 빠지거나 제대로 된 number 값이 아닌0을 출력할 수도 있습니다.
메모리 가시성(memory visibility)과 재정렬(reordering)문제로 인해 위와 같은 현상이 생기게 됩니다.
Memory Visibility
앞서 본 예시에서는 main thread와 reder thread 두개의 쓰레드가 존재합니다.
운영체제가 두개의 쓰레드를 다른 CPU 코어 에서 동작시킨다고 생각해보겠습니다.
- 메인 쓰레드는 ready와 number 변수의 복사본을 cpu cache에 복사합니다.
- 리더 쓰레드 또한 복사본을 cpu cache에 복사합니다.
- 메인 쓰레드가 캐시된 값들을 수정합니다.
위 그림에서, 그리고 과정에서 알 수 있는 문제점은 CPU1과 CPU2에서 각각의 변수에 어떤 내용이 표시될지 보장할 수 없다는 문제가 있습니다.
또한 리더 쓰레드에서는 코드의 실행순서와 다른 순서로 실행이 될 수도 있습니다.
public static void main(String[] args) {
new Reader().start();
number = 42;
ready = true;
}
위 코드의 순서대로 실행된다면 reader 쓰레드에서 42를 출력하게 될 것입니다. 하지만 실제로는 0을 출력하게 될 수도 있습니다.
왜냐하면 Reordering이 일어날 수도 있기 때문입니다.
- JIT Compiler는 재정렬을 통해 최적화를 할 수 있기 때문에 Reordering이 일어날 수도 있습니다.
그렇다면 이런 문제들을 어떻게 해결할 수 있을까요?
volatile Memory Order
자바에서는 volatile 키워드를 사용함으로 캐시 일관성 문제를 해결할 수 있습니다.
public class TaskRunner {
private volatile static int number;
private volatile static boolean ready;
// same as before
}
위와 같이 변수에 volatile 키워드를 선언해줌으로써, CPU cache에 변수를 저장하는 것이 아닌, 바로 RAM에 저장할 수 있게합니다. 그리고 변경사항이 발생하면 해당 변경사항을 RAM에 바로 저장하게 됩니다
volatile은 언제 쓰는게 좋을까?
한쓰레드에서만 값에 대한 쓰기 및 변경이 일어나고, 다른 쓰레드에서는 읽기만 하는 경우에 적합할 것입니다. 왜냐하면 앞서 본 예시처럼 volatile 키워드를 사용하면 RAM에 바로 해당 데이터를 저장하기 때문에 값을 읽는 쓰레드에서는 동일한 값을 볼 수 있기에 적합합니다.
그리고 volatile로 선언된 변수는 바로 RAM에서 읽거나 쓰게 되기 때문에, CPU cache에서 데이터를 읽는 것보다 성능 저하가 발생하고, reordering을 통한 최적화 작업도 불가능하게 하기 때문에 필요한 변수에 선언을 해주는 것이 좋을 것 입니다.
'프로그래밍 > Java' 카테고리의 다른 글
소수 계산 어떻게 해결해야할까? (0) | 2023.09.16 |
---|---|
함수형 프로그래밍 (0) | 2023.04.11 |
G1 GC에 대해 (0) | 2023.03.31 |
Singleton 패턴과 스프링에서는 Singleton 패턴을 어떻게 사용하고 있을까? (0) | 2023.01.27 |
Long 과 AtomicLong은 어떤 차이가 있을까? (0) | 2023.01.24 |