여러 개의 스레드에서 특정 객체를 동시에 사용하려 할 때 섞이지 않고 안전하게 동작하도록 객체를 공유하고 공개하는 방법
소스코드의 특정 블록을 동기화시키고자 할 때는 항상 메모리 가시성 문제가 발생한다.
메모리 상의 공유된 변수를 여러 스레드에서 서로 사용할 수 있게 하려면 반드시 동기화 기능을 구현해야 한다.
동기화 작업이 되어있지 않은 상태에서, 여러 스레드가 동일한 변수를 사용할 때 문제가 생기는 경우
public class NoVisibility {
private static boolean ready;
private static int number;
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 = 42;
ready = true;
}
}
메인 스레드는 읽기 스레드를 실행한 다음 number , ready 변수의 값을 지정하고, 읽기 스레드는 ready 의 값이 true 가 될 때까지 기다리다가 number 변수의 값을 출력한다.
읽기 스레드는 42 라는 값을 출력하리라고 생각할 수 있지만, 0 이라는 값을 출력할 수도 있고, 영원히 값을 출력하지 못하고 계속 기다릴 수도 있다.
- 읽기 스레드가 메인 스레드에서 ready → number 변수 순으로 저장한다고 볼 경우 : 42
2개의 스레드에서 변수를 공유해 사용하지만 적절한 동기화 기법을 사용하지 않았기 때문이다.
재배치 (reordering)
⇒ 여러 스레드에서 공동으로 사용하는 변수에는 항상 적절한 동기화 기법을 적용해야 한다.
변수를 사용하는 모든 경우에 동기화를 시켜두지 않으면 해당 변수에 대한 최신 값이 아닌 다른 값을 사용하게 되는 경우가 발생할 수 있다.
스테일 데이터를 사용하게 될 때도 있고, 정상적으로 동작하는 경우도 있어 문제가 된다.
[예제] MutableInteger 클래스에서 get() 과 set() 이 동기화되지 않은 경우
@NotThreadSafe
public class MutableInteger{
private int value;
public int get() { return value; }
public void set(int value) { this.value = value; }
}
[예제] SynchronizedInteger 클래스에서 get() 과 set() 이 동기화된 경우
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private int value;
public synchronized int get() { return value; }
public synchronized void set(int value) { this.value = value; }
}
동기화되지 않은 상태에서 특정 스레드가 변수의 값을 읽으려하면 스테일 상태의 값을 읽어갈 가능성이 있지만, 64비트를 사용하는 숫자형 (double, long) 에 volatile 을 사용하지 않으면 난데없는 값마저 생길 가능성이 있다.