Search

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

생성일
2023/08/23 06:05
챕터
11장 -동시성

요약

지연 초기화는 성능 개선이 필요한 순간까지 하지 말자.

지연 초기화(lazy initialization)란

지연 초기화는 필드의 초기화 시점을 해당 값이 처음 필요한 순간까지 미루는 기법이다. 그렇기 때문에 값을 사용하지 않으면 초기화도 발생하지 않는다
지연 초기화는 주로 최적화 용도로 사용되지만, 클래스와 인스턴스 초기화 때 발생하는 순환 참조 문제를 해결하는 효과도 있다.

지연 초기화의 한계

클래스 혹은 인스턴스 생성 시의 초기화 비용은 줄어들지만, 처음 초기화가 필요한 호출 시 필드에 접근하는 비용이 증가한다.
실제 초기화에 드는 비용과 초기화된 필드들에 얼마나 자주 접근하는 가에 따라 실제로는 성능이 느려질 수도 있다.
지연 초기화가 필요한 순간은 해당 클래스의 인스턴스 중 그 필드를 사용하는 인스턴스가 적지만 해당 필드의 초기화 비용이 큰 경우이다.
이런 경우를 알아볼 수 있는 방법은 특별히 없으므로 실제 지연 초기화 전후 성능 비교를 해보는 방법 뿐이다.
지연 초기화는 최적화로 성능 개선이 필요한 순간까지 하지 말자.

멀티 스레딩 환경의 지연 초기화

멀티 스레딩 환경에서는 지연초기화 하기에 까다롭고, 지연 초기화로 인해 심각한 버그로 이어질 수 있다.
대부분 상황에서 일반적인 초기화가 지연 초기화보다 낫다.
// 일반적인 초기화 private final FieldType field = computeFieldValue(); // 지연 초기화 private final FieldType field; private synchronized FieldType getField() { if (field == null) field = computeFieldValue(); return field; }
Java
복사
위 코드는 지연 초기화로 인해 초기화 순환성(initialization circularity)을 깨뜨릴 것 같은 경우 synchronized 접근자를 사용한 것이다.
초기화 순환성 class A instance → class B instance → class C instance
성능 때문에 정적 필드를 지연 초기화 해야한다면 지연 초기화 홀더 클래스(lazy initialization holder class)를 사용하자
private static class FieldHolder { static final FieldType field = computeFieldValue(); } private static FiledType getField() { return FieldHolder.field; }
Java
복사
지연 초기화 홀더 클래스는 어떤 클래스를 사용 전까지 초기화 하지 않고, 처음 클래스에 접근할 때 초기화를 진행하는 특성을 이용한 방법이다.
또한 동기화를 하지 않기 때문에 성능이 느려지지 않는다. VM 자체에서 처음 접근 시에만 동기화를 진행하고, 그 이후 동기화 코드를 제거하여 아무런 검사나 동기화 없이 필드에 접근하기 때문이다.
성능 때문에 인스턴스 필드를 지연 초기화 해야한다면 이중 검사(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; } }
Java
복사
이중 검사는 동기화 없이 한 번 검사하고 동기화하여 두 번째 검사하는 방법이다.
초기화 이후로는 동기화하지 않기 때문에 반드시 해당 필드를 volatile로 선언해야한다.
result 변수를 사용하는 이유는 한 번만 읽는 것을 보장하기 위함이다.
private volatile FieldType field; private FieldType getField() { FieldType result = field; if (result == null) field = result = computeFieldValue(); return result; }
Java
복사
반복해서 초기화해도 상관이 없는 인스턴스 필드라면, 위와 같이 두 번째 검사를 생략해서 단일 검사(single-check)를 할 수도 있다. 이 경우에도 volatile로 선언해야한다.