Skip to content

Latest commit

 

History

History
86 lines (62 loc) · 4.94 KB

item78_79_82_83.md

File metadata and controls

86 lines (62 loc) · 4.94 KB

ITEM 78 : 공유 중인 가변 데이터는 동기화해 사용하라

  1. synchronized
    쓰기와 읽기 모두가 동기화되지 않으면 동작을 보장하지 않는다. 둘 다 동기화해서 사용해야 한다.

  2. volatile
    volatile 한정자는 배타적 수행과는 상관없지만 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다.

1개의 쓰레드에서만 쓰기를 하고 나머지에서는 읽기만 한다면 volatile을 사용하는게 좋지만 그 외에는 동기화를 위해 synchronized를 사용해야 한다.

핵심 정리

여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화 해야 한다.

ITEM 79 : 과도한 동기화는 피해라

응답 불가와 안전 실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 클라이언트에 양도하면 안 된다.

핵심 정리

동기화 영역 안에서의 작업은 최소한으로 줄이자. 가변 클래스를 설계할 때는 스스로 동기화해야 할지 고민하자. 멀티코어 세상인 지금은 과도한 동기화를 피하는게 과거 어느 때보다 중요하다. 합당한 이유가 있을 때만 내부에서 동기화하고, 동기화했는지 여부를 문서에 명확히 밝히자

ITEM 82 : 스레드 안전성 수준을 문서화하라

  • 불변 : 이 클래스의 인스턴스는 마치 상수와 같아서 외부 동기화도 필요 없다. String, Long, BihInteger
  • 무조건적 스레드 안전 : 이 클래스의 인스터스는 수정될 수 있으나, 내부에서 충실히 동기화하여 별도의 외부 동기화 없이 동시에 사용해도 안전하다. AtomicLong, ConcurrentHashMap
  • 조건부 스레드 안전 : 무조건적 스레드 안전과 같으나, 일부 메서드는 동시에 사용하려면 외부 동기화가 필요하다 Collections.synchronized 래퍼 메서드가 반환한 컬렉션들이 여기 속한다.
  • 스레드 안전하지 않음 : 이 클래스의 인스턴스는 수정될 수 있다. 동시에 사용하려면 각각의 메서드 호출을 클라이언트가 선택한 외부 동기화 메커니즘으로 감싸야 한다. ArrayList, HashMap

ITEM 83 : 지연 초기화는 신중히 사용하라

지연 초기화는 필드의 초기화 시점을 그 값이 처음 필요할때까지 늦추는 기법이다. 그래서 값이 전혀 쓰이지 않으면 초기화도 결코 일어나지 않는다.

멀티스레드 환경에서는 지연 초기화를 하기가 까다롭다. 지연 초기화하는 필드를 둘 이상의 스레드가 공유한다면 어떤 형태로든 반드시 동기화해야 한다.

  1. 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다.
  2. 성능 떄분에 정적 필드를 지연 초기화해야 한다면 지연 초기화 홀더 클래스 (lazy initialization holder class)관용구를 사용하자.
private static class FieldHolder {
    static final FieldType field = computerFieldValue();
}

private static FieldType getField() { return FieldHolder.field; }

getField가 처음 호출되는 순간 FieldHolder.field가 처음 읽히면서, 비로소 FieldHolder 클래스 초기화를 촉발한다. 이 관용구의 멋진 점은 getField 메서드가 필드에 접근하면서 동기화를 전혀 하지 않으니 성능이 느려질 거리가 전혀 없다는 것이다. 일반적인 VM은 오직 클래스를 초기화할 때만 필드 접근을 동기화할 것이다. 클래스 초기화가 끝난 후에는 VM이 동기화 코드를 제거하여, 그다음부터는 아무런 검사나 동기화 없이 필드에 접근하게 된다.

  1. 성능 때문에 인스턴스 필드를 지연 초기화해야 한다면 이중검사 (double-check) 관용구를 사용하라.
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result != null) { // 첫 번째 검사 (락 사용 안함)
        return result;
    }

    synchronized(this) {
        if (field == null) // 두 번째 검사 (락 사용)
            field = computeFieldValue();
        return field;
    }
}
  1. 반복해서 초기화 해도 상관없는 인스턴스 필드를 지연 초기화해야 할 경우 단일검사 (single-check) 관용구를 사용하자.
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (field == null)
        field = computeFieldValue();
    return field;
}

핵심 정리

대부분의 필드는 지연시키지 말고 곧바고 초기화해야 한다. 성능 때문에 혹은 위험한 초기화 순환을 막기 위해 꼭 지연 초기화를 써야 한다면 올바른 지연 초기화 기업을 사용하자. 인스턴스 필드에는 이중검사 관용구를, 정적 필드에는 지연 초기화 홀더 클래스 관용구를 사용하자. 반복해 초기화해도 괜찮은 인스턴스 필드에는 단일검사 관용구도 고려 대상이다.