Item 66. 변경 가능 공유 데이터에 대한 접근은 동기화하라
  • 상호 배제성뿐 아니라 스레드 간의 안정적 통신을 위해서도 동기화는 반드시 필요하다.

  • 읽기 연산과 쓰기 연산에 전부 적용하지 않으면 동기화는 아무런 효과도 없다.

동기화를 구현하는 방법

synchronized

// 적절히 동기화한 스레드 종료 예제
public class StopThread {
    private static boolean stopRequested;
    private static synchronized void requestStop() {
        stopRequested = true;
    }
    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args)
            throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while(!stopRequested())
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

volatile

// 적절히 동기화한 스레드 종료 예제
public class StopThread {
    private static volatile boolean stopRequested;
    public static void main(String[] args)
            throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while(!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

volatile은 상호배제를 구현하지 않지만 가장 최근에 기록된 값을 조회하도록 보장한다.

잘못된 volatile 사용

// 잘못된 예제 - 동기화가 필요하다!
private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}

문제는 ++연산자가 원자적이지 않다는데 있다. 이 연산자는 두가지 연산을 순서대로 시행한다.

  1. 증가할 값을 읽는다.
  2. 새로운 값(읽은 값에 +1을 더해서)을 필드에 쓴다.

첫 번째 쓰레드가 필드의 값을 읽은 후 새 값을 미처 기록하기 전에 두 번째 스레드가 필드에서 같은 값을 읽으면, 두 스레드는 같은 일련번호를 얻게 될 가능성이 있다.

위와 같은 문제를 해결 하는 방법

동기화 솔루션의 역발상

변경 가능 데이터를 공유하지 않는 것 - 동시성 이슈가 발생하지 않음

  1. 변경 불가능 데이터(불변객체)를 공유
  2. 변경 가능한 데이터는 한 스레드만 이용하도록 한다 (공유하지 않음)

실질적으로 변경 불가능한 객체(effectively immutable)

특정한 스레드만이 데이터 객체를 변경할 수 있도록 하고, 변경이 끝난 뒤에 다른 스레드와 공유하도록 할 때는 객체 참조를 공유하는 부분에만 동기화를 적용함 객체 참조를 가져온 스레드는, 객체가 더 이상 수정되지 안흔 한 동기화 없이도 객체의 내용을 읽을 수 있음, 이런 객체를 실질적으로 변경 불가능한 객체(effectively immutable) 라고 함

결론

변경 가능한 데이터를 공유할 때는 해당 데이터를 읽거나 쓰는 모든 스레드는 동기화를 수행해야 한다.