Synchronization and the Java Memory Model

멀티 스레드 환경에서 특정 변수의 값을 가져갈 때 다른 스레드가 작성한 값을 가져갈 수 있다는 보장도 없고, 심지어는 값을 읽지 못하는 경우도 있을 수 있다.

따라서 메모리상의 공유된 변수를 여러 스레드에서 서로 사용할 수 있게 하려면 반드시 동기화 기능을 구현해야 한다.

변수 가시성 예제

public class NoVisibility {
    private static boolean ready;           // 여러 스레드간 공유되는 변수 1
    private static int number;              // 여러 스레드간 공유되는 변수 2
  
    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready) {
                Thread.yield();
            }
  
            System.out.println(number);
        }
    }
  
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 100;
        ready = true;
    }
}

main 스레드에서 분명 number = 100 / ready = true 로 설정하였지만, ReaderThread 에서 이 값을 읽지 못하여 무한 루프를 돌거나, 루프를 빠져나왔다 하더라도 number가 100이 아닌 이상한 값으로 되어있는 경우가 있다.

이러한 현상이 일어나는 원인은 재배치 현상 때문으로 컴파일러나 JVM이 최적화등을 위해 명령어 실행 순서를 바꾸기 때문이다. 명령어 재배치는 멀티 스레드라는 것을 가정하지 않고 이루어지기 때문에 동기화 문제가 발생할 수 있다.

Stale 데이터

제대로 동기화가 이루어 지지 않은 환경에서 잘못된 값을 읽어가는 것을 Stale 데이터라고 한다.

DB에서 일종의 READ_UNCOMMITED 와 같은 격리성과 비슷

원자적이지 않은 64비트 연산

intboolean 등의 자료형이 동기화 되지 않으면 이전 스레드에서 설정한 값을 가져간다.

그러나 64비트 자료형 (double, long 등)의 경우에는 아예 오염된 쓰레기 값을 읽어가는 경우가 있을 수 있다. 왜냐하면, 64비트 자료형에 대해서는 값을 쓰거나 읽을 때 2번의 걸친 32비트 연산을 사용하도록 허용하고 있기 때문이다.

따라서 2번의 32비트 연산이 다른 시점(이전시점, 최신시점)의 값을 읽어오게 되면 전혀 엉뚱한 값이 읽힐 수 있다.

volatile 변수

volatile 키워드를 사용하여 선언된 변수는 "이 변수는 공유해 사용되기 때문에 실행순서 재배치를 해서는 안된다"고 컴파일러와 JVM에 알려준다. 그래서 스레드가 항상 최신의 값을 가져갈 수 있도록 보장한다. 가장 약한 동기화 수준이다.