Skip to content

[아이템 83] 지연 초기화는 신중히 사용하라 #83

@janeljs

Description

@janeljs

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

지연 초기화

  • 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
  • 값이 안 쓰이면 초기화도 X
  • 정적 필드와 인스턴스 필드 모두에 사용 가능
  • 최적화나 클래스&인스턴스 초기화 때 발생하는 순환문제 해결하는 효과

지연 초기화가 필요할 때까지는 하지 말라

  • 클래스 또는 인스턴스 생성 시 초기화 비용은 줄지만 필드에 지연초기화하는 필드에 접근하는 비용은 커짐
  • 대부분 일반적인 초기화가 낫다

지연 초기화가 필요할 때

  • 지연 초기화하려는 필드를 사용하는 인스턴스 비율은 낮은데 초기화 비용은 큰 경우
  • 여러 스레드가 지연 초기화 필드를 공유하면 버그 발생 가능 (동기화해야 함)

인스턴스 필드를 초기화하는 일반적인 방법

private final FieldType field = computeFieldValue();

인스턴스 필드의 지연 초기화 - synchronized 접근자 방식

지연 초기화가 초기화 순환성(initialization circularity)을 깨뜨릴 것 같으면 synchronized를 단 접근자를 사용하자.

초기화 순환성 깨기 위해 지연초기화를 사용하고 싶다면 synchronized를 사용하라는 것 같음

private FieldType field;

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

정적 필드용 지연 초기화 홀더 클래스 관용구

지연 초기화 홀더 클래스(lazy initialization holder class) 관용구: 클래스는 클래스가 처음 쓰일 때 비로소 초기화된다는 특성을 이용한 관용구

private static class Fieldholder {
    static final FieldType field = computeFieldValue();
}

private static FieldType getField() { return FieldHolder.field;}
  1. getField 호출
  2. FieldHolder.field가 처음 읽히면서, FieldHolder 클래스 초기화
  • getField() 메서드가 필드에 접근하면서 동기화를 사용하지 않아 성능 저하 우려 X
  • 대부분 VM은 클래스를 초기화할 때만 필드 접근을 동기화
  • 클래스 초기화가 끝난 후에는 VM이 동기화 코드를 제거하여, 그 다음부터는 아무런 검사나 동기화 없이 필드에 접근

인스턴스 필드 지연 초기화용 이중검사 관용구

  • 초기화된 필드에 접근할 때의 동기화 비용을 없애준다.
  • 첫 번째는 동기화 없이, 두 번째는 동기화하여 필드의 값을 검사
  • 두 번째 검사에서도 초기화 안 된 경우만 필드를 초기화
  • 필드 초기화 이후에는 동기화하지 않으므로 volatile로 선언 (java 변수를 cpu cache가 아닌 메인 메모리에 저장)
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result != null) { // 첫 번째 검사 (락 사용 안 함)
        return result;
    
    synchronized(this) {
        if (field == null) // 두 번째 검사 (락 사용)
            field = computeFieldValue();
        return field;
    }
}
  • result: 필드가 이미 초기화된 상황에서 해당 필드를 한번만 읽도록 보장하는 역할

단일검사(single-check) 관용구

  • 반복해서 초기화해도 상관없는 인스턴스 필드를 지연초기화해야 할 때 사용
  • 두 번째 검사 생략
  • 필드는 volatile로 선언
private volatile FieldType field;

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

짜릿한 단일검사(Racy Single-Check) 관용구

핵심 정리

  • 대부분의 필드는 지연시키지 말고 바로 초기화하자.
  • 꼭 지연 초기화를 써야 한다면 인스턴스 필드에는 이중검사 관용구를, 정적 필드에는 홀더 클래스 관용구를 사용하자.
  • 반복해 초기화해도 괜찮은 인스턴스 필드에는 단일검사 관용구를 사용해도 된다.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions