78. 변경 가능한 공유 데이터는 동기화하라

synchronized

  • synchronized 키워드는 해당 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장한다.

  • 2가지 주요 기능

    • 배타적 수행

      • 한 쓰레드가 사용시 다른 쓰레드가 들어오지 못하게 막는다.

    • 스레드 간 안정적인 통신

      • 일관성이 깨진 상태를 read 하는 것을 막아준다.

문제가 있는 코드

아래 코드는 1초후에 종료가 될까?

// 예상과 다르게 작동하는 코드
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;
  }
}

이유

메인스레드가 수정한 값을 백그라운드 스레드가 언제쯤에나 보게될지 보증 불가 하다.

그리고 JVM은 다음과 같이 최적화를 수행하기도 한다.

개선된 코드

  • 쓰기와 읽기 메소드 모두 synchronized 필요

더 나은 대안

  • 위 반복문에서 지속적인 동기화를 함으로, 느릴 수 있다.

  • volatile 를 사용하자.

    • 항상 최근에 기록된 값을 읽도록 보장

volatile 사용시 주의사항

  • 아래 코드 예제는 유일한 일련번호를 리턴해준다.

  • generateSerialNumber를 한 스레드가 접근했을때, nextSerialNumber을 읽고/쓰는 작업을 수행한다.

    • 그런데, 중간에 다른 스레드가 비집고 와서 실행된다면?

    • 같은 값을 받게 된다.

  • 발생 이유

    • volatile 은 동기화의 2가지 기능 중 통신 쪽만 지원하기에 베타적 수행은 해결해주지 않는다.

  • 해결법

    • 메소드에 synchronized 추가하고 변수에 volatile 은 제거

    • 추가적으로, int 대신 long 사용이 더 견고함

아이템59 조언에 따라 - atomic

java.util.concurrent.atomic 패키지를 사용해보자.

  • 락없이도 안전

조언

  • mutable 데이터는 공유하지 않는 것이 좋다. 즉, 가변 데이터는 단일 스레드에서만 쓰자.

Last updated