Item 83. Lazy Initialization

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

일반적으로 필드를 초기화 할 때 아래와 같이 초기화를 한다.

class Init {

    private final FieldType field = computeFieldValue();
}

하지만 초기화 시점을 처음에 하는 것이 아니라 필요한 시점에 하는 지연 초기화 기법을 사용할 수 있다. 다른 최적화와 마찬가지로 지연 초기화는 모든 상황에서 성능을 향상시키지 않으며, 오히려 성능을 저하시킬 수도 있다. 지연 초기화는 보통 아래와 같은 경우에 사용한다.

  • 필드의 초기화 비용이 높은 경우 최적화 목적으로 사용

  • 순환 문제를 해결해야 하는 경우

초기화 순환성(Initialization circularity) 해결 방법

초기화 순환성 문제를 해결하기 위해 지연 초기화를 사용하는 경우, 아래와 같이 synchornized 키워드를 사용하면 간단하게 해결할 수 있다.

class LazyInit {

    private FieldType field;

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

성능 문제로 인한 지연 초기화(정적 필드)

만약 성능 문제로 정적 필드를 지연 초기화해야하는 경우엔 지연 초기화 홀더 클래스 관용구를 사용할 수 있다. 클래스는 클래스가 처임 쓰일 때 초기화되는 특성을 이용하여 필드를 지연 초기화하는 방법이다.

class LazyInit {

    private static class FieldHolder {

        static final FieldType field = computeFieldValue();
    }

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

getField 메서드가 처음 호출될 때 FieldHolder 클래스가 초기화되며, 내부의 field 필드가 초기화된다. 이 방식은 동기화 없이도 지연 초기화를 수행할 수 있어, 성능이 저하될 요인이 전혀 없다는 장점이 있다.

성능 문제로 인한 지연 초기화(인스턴스 필드)

인스턴스 필드의 경우엔 이중검사 관용구를 사용해 지연 초기화를 수행할 수 있다. 이 방식은 한 번 초기화된 필드에 접근할 때 동기화 비용을 없애주는 방식이다.

class LazyInit {

    private volatile FieldType field;

    private FieldType getField() {
        FieldType result = field; // 지역 변수를 사용해 필드를 한 번만 읽도록 보장
        if (result != null) { // 첫 번째 검사: 락 없이 수행
            return result; // null이 아니면 이미 초기화된 상태이므로 반환
        }

        synchronized (this) { // 두 번째 검사: 락을 걸고 수행
            if (field == null) { // 필드가 아직 초기화되지 않았다면
                field = computeFieldValue(); // 필드를 초기화
            }
            return field; // 필드를 반환
        }
    }
}

volatile 키워드를 사용하여 필드를 선언하여, 필드에 대한 모든 읽기/쓰기가 메인 메모리에서 수행되기 때문에 이중검사 관용구를 사용할 수 있게 된다. 이 방식은 정적 필드에도 사용할 수 있지만, 굳이 그럴 필요 없이 정적 필드의 경우엔 지연 초기화 홀더 클래스 관용구를 사용하는 것이 더 좋다.

Last updated

Was this helpful?