카테고리 없음

[Effective Java] Item78. 공유중인 가변 데이터는 동기화해 사용하라

📝 작성 : 2022.09.18  ⏱ 수정 : 

synchronized

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(() -> {
          int i = 0;
          while (!stopRequested())
            i++;
        });
        backgroundThread.start();

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

쓰기와 읽기 모두 동기화해야 동작을 보장할 수 있습니다. 이때, volatile 한정자를 사용해 동기화에 대한 비용을 줄일 수 있습니다.

volatile 필드 사용


public class StopThread {
    private static volatile 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;
    }
}

volatile 사용시 주의사항

private static volatile int nextSerialNumber = 0;

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

증가 연산자(++)사용시 nextSerialNumber필드에 두 번 접근(값을 읽고, 1증가한 값을 저장)합니다. 이때, 두 번째 스레드가 두 접근 사이를 비집고 들어와 값을 읽어가면 첫 번쨰 스레드와 똑같은 값을 돌려받습니다. 이런 경우 generateSerialNumber 메서드에 synchronized 한정자를 붙이고 volatile을 제거합니다.

java.util.concurrent.atomic을 이용한 락-프리 동기화

private static final AtomicLong nextSerialNumber = new AtomicLong();

public static long generateSerialNumber() {
      return nextSerialNumber.getAndIncrement();
}

java.util.concurrent.atomic을 사용하면 동기화 버전보다 성능이 좋으며 원자성까지 지원합니다.

반응형