이펙티브 자바 동시성 챕터 요약

공유하는 가변 데이터에 접근 시 동기화 하자

  • 쓰는 메소드 뿐만 아니라 읽는 메소드도 동기화 처리를 해야 한다.
  • 32비트 변수에 대한 할당 연산은 원자적으로 처리되기 때문에 동기화가 필요하지 않다.
  • 하지만 스레드 간에 공유되는 데이터라면 메모리 가시성을 위해서 동기화 처리가 필요하다.
  • 메모리 가시성만 확보하면 되기 때문에 volatile 키워드를 사용해도 된다.
  • int 변수에 대한 ++ 연산은 단일 연산처럼 보이지만 실제로는 3단계로 나뉘어져 있기 때문에 동기화 처리가 필요하다.

 

지나친 동기화는 피하자

동기화 블록 안에서 외계인 메소드 호출을 하지 말아야 한다.

 

wait와 notify 대신 동시성 유틸리티를 사용하자

  • wait, notify를 사용할 이유가 거의 없다. 직접 사용하는 것은 "동시성 어셈블리 언어"로 프로그래밍 하는 것과 같다.
  • 올바르게 사용하기 어렵다면 고수준 동시성 유틸리티를 사용해야 한다.
    • ConcurrentHashMap, BlockingQueue, CountDownLatch, Semaphore, CyclicBarrier, Exchanger 등
  • 새로 작성하는 코드에는 가급적 사용하지 말고 유지보수 한다면 다음을 지키자.
  • 표준 이디엄을 사용해서 while 루프에서 wait 메소드를 호출
  • 일반적으로 notify보다는 notifyAll을 사용해야 함
  • notify를 사용한다면 스레드 활동성 보장에 신경써야함

 

스레드 안전을 문서화 하자

  • 모든 클래스는 자신의 스레드 안전 속성을 명확하게 문서화해야 함.
  • 내용을 작성할 때는 신중하게 문장을 기술하거나 스레드 안전 주석을 사용한다.
  • synchronized는 javadoc에 나오지 않기 때문에 알 수 없다.

 

스레드 안전 수준 5가지 (일반적인 경우)

  • 불변
  • 무조건적 스레드 안전
  • 조건적 스레드 안전
  • 스레드 안전하지 않음
  • 스레드 적대

조건적 스레드 안전 클래스는 외부 동기화가 필요할 수 있으므로 락 획득 필요성과 순서에 대해 문서화해야함
무조건적 스레드 안전 클래스는 동기화된 메소드 대신 private 락 객체를 사용하면 클라이언트나 서브 클래스에서 동기화를 방해하는 것을 막을 수 있다.

 

늦 초기화를 분별력 있게 사용하자

  • 늦 초기화는 본래 최적화 관점에서 필요하지만 인스턴스가 완벽하게 생성되지 않은 상태에서 사용되는 것을 방지하는 목적으로 사용될 수도 있음
  • 대부분의 최적화에서 그렇듯 "필요하지 않으면 하지 말자".
  • 다중 스레드에서 늦 초기화는 쉽지 않고 대부분의 상황에서는 정상적인 초기화가 늦 초기화보다 좋다.
  • 만약 사용해야 한다면 다음과 같은 방법을 사용하자.
    • 인스턴트 필드에 적용할 때는 synchronized 키워드로 동기화 처리
    • static 필드에 적용한다면 늦 초기화 홀더 클래스 이디엄을 사용
private static class FieldHolder {

static final FieldType field = computeFieldValue();

}

static FieldType getField() { return FieldHolder.field; }
  • 근래의 VM은 초기화하는 필드의 접근만 동기화 시킴.
  • 일단 클래스가 초기화되면 이후에 그 필드 접근 시는 동기화 코드가 포함되지 않도록 VM이 코드를 조정함
  • 인스턴트 필드의 성능을 고려한다면 더블 체크락을 사용(자바 병렬 프로그래밍 책에서 더블 체크락은 문제가 있다고 나와있었는데)

 

스레드 스케줄러에 의존하지 말자

  • 프로그램의 정확성을 스레드 스케줄러에 의존하면 안된다. 의존하는 프로그램은 강력하지 않고 이식성도 없다.
  • Thread.yield 메소드, 스레드 우선 순위에도 의존하지 말자.
  • 우선 순위 조정으로 응답성을 조절할순 있지만 이식성도 떨어지는데다 근본적인 원인을 찾아서 해결하는게 답이다.

 

스레드 그룹을 사용하지 말자

  • 스레드 그룹은 원래 보안을 목적으로 애플릿을 격리시키는 메커니즘으로 구성되었으나 지금은 거의 쓸모없는 기능이다.
  • 단지 Thread 클래스의 기본 메소드들을 여러 스레드에 일괄 적용하는 기능이 있지만 스레드 안전 관점에서 빈약하다.
  • 한마디로, 스레드 그룹을 쓸모없다. 스레드를 논리적인 그룹으로 묶어서 처리해야한다면 ThreadPoolExecutor를 사용하자.