Volatile

Volatile 이란?

 

public volatile Integer sample = 0;

 

volatile 키워드를 선언한 sample 이라는 변수의 값은 컴파일 시점 최적화를 무시하고 항상 메인 메모리에 Read/Write 된다.

 

 

메인 메모리에만 Read/Write 된다는 것은 무슨 의미일까?

 

JVM 의 Runtime Data Area 내에는 Stack Area, Heap Area 가 존재한다.

지역 변수, 매개변수 등의 정보는 Stack Area 내 존재 하며 Heap Area 에 적재된 값을 참조한다.

 

결론적으로 Heap Area 에 적재된 메모리는 하드웨어 어딘가에 적재되거나 읽어진다.

이 하드웨어 '어딘가'는 CPU의 캐시 메모리 이거나 코어 내 레지스터 일 수 있고 메인 메모리일 수도 있다.

즉, 컴파일 시점에 실제 작성된 개발 코드 최적화가 수행 전략에 따라 이 세곳중 어딘가에는 적재되고 또 어딘가로 부터 읽어진다.

 

이는 개발자가 예측하기 어렵다.

 

아래 그림은 CPU의 논리적구조를 표현한 그림이며

작업중인 스레드들은 실제 레지스터, 캐시 메모리, 메인 메모리로 부터 데이터를 Read/Write 할 수 있다는 것을 나타낸다.

 

 

 

어디에 Read/Write 되는지 예측하기 어렵다면 무슨 문제가 발생될까?

 

public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested)
                i++;
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}
// 출처 : 이펙티브 자바 3판

 

위의 코드 실행시 어떤 결과가 나올지 예상이 가는가?

마지막줄에 stopRequested를 true로 할당함으로써 backgroundThread의 while 문이 종료될것 같지만, 결론적으로 해당 코드는 무한 루프에 빠진다.

 

이유는 Main Thread, background Thread 간 서로 stopRequested 라는 값을 읽고 있는 공간이 다르기 때문이다.

예를들어 최적화 전략에 따라 background Thread 는 stopRequested 값을 레지스터에서 가져오는 반면 Main Thread가 업데이트 한 stopRequested는 L2 캐시메모리에만 반영되어 있을 수 있다. (실제 읽는 공간은 상이할 수 있음)

 

위 같은 멀티스레환경의 문제점인 메모리 가시성을 보장하지 못하며 volatile은 이 메모리 가시성을 보장하기 위해 사용된다.

 

 

메모리 가시성이란?

변경된 실제 값을 스레드간 서로 읽는 공간이 다름으로써 스레드간 변경된 값에 대해 파악하지 못하면 메모리 가시성을 보장하지 못한다고 말한다.

 

 

즉, 아래와같이 volatile 키워드만 선언해두면 stopRequested 변수는 컴파일시점에 최적화에서 제외되고 stopRequested의 Read/Write는 메인 메모리를 통해 이루어지게 된다. 

 

public class StopThread {
    private volatile static boolean stopRequested; //volatile 선언

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested)
                i++;
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}
// 출처 : 이펙티브 자바 3판

 

 결과적으로  Main Thread, background Thread 둘 다 레지스터나 캐시메모리가 아닌 메인메모리로 부터 읽고 쓰게되며 Main Thread 가 업데이트 한 값을 background Threa가 알 수 있게 됨으로써 해당 코드는 무한 루프에 빠지지 않는다.

 

 

 

그럼 멀티 스레드 환경에서 volatile을 무조건 선언하는게 좋은건가?

 

그렇지 않다. volatile은 메모리 가시성을 위해 메인 메모리에 Read/Write 하는 것일 뿐 동시성 문제는 여전히 드러난다.

그러므로 Write는 한 스레드만 수행하고 이외에 스레드는 Read-Only를 강제시켜야 안전하다.

 

추가적으로 sychronized 또는 Atomic 클래스를 사용해도 무방하다.

 

 

 

마치며

 

틀린점이 있다면 피드백 부탁드립니다. 🙏🏻