Item 71. 초기화 지연은 신중하게 하라.
- 초기화 지연(lazy initialization) : 필드 초기화를 실제로 그 값이 쓰일 때 까지 미루는 것
- 대부분 일반적인 초기화를 진행하는 것이 좋으므로 꼭 필요할 때만 초기화 지연을 사용하자.
초기화 순환성(initialization circularity) 문제를 해소하기 위해서 초기화를 지연시키는 경우에는 동기화 된 접근자(synchronized accessor)를 사용하라.
// 동기화된 접근자를 사용한 객체 필드 초기화 지연 방법
private FieldType field;
synchronized FieldType getField() {
if (field == null)
filed = computeFieldValue();
return field;
}
- 안전하지만
field
를 항상 동기화 하기 때문에 성능이 나쁨.
성능문제 때문에 정적 필드 초기화를 지연시키고 싶을 때는 초기화 지연 담당 클래스(lazy initialization holder class)숙어를 적용하라. (요청 기반 초기화 담당 클래스(initialize-on-demand holder class)숙어라고도 함)
// 정적 필드에 대한 초기화 지연 담당 클래스 숙어
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }
- 위 숙어의 좋은 점은
getField
함수가 처음 호출되는 순간에 초기화 된다응 점이다. 따라서, 초기화를 지연시켜도 함수 이용 비용은 전혀 증가하지 않는다. - 단점은 클래스가 일단 초기화되고 나면, 재 초기화 하기가 힘들다.
성능 문제 때문에 객체 필드 초기화를 지연시키고 싶다면 이중 검사(double check)숙어를 사용하라.
// 이중 검사 패턴을 통해 객체 필드 초기화를 지연시키는 숙어
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // 첫 번째 검사 (락 없음)
synchronized(this) {
result = field;
if (result == null) // 두 번째 검사 (락 있음)
field = result = computeFieldValue();
}
}
return result;
}
- 지역 변수
FieldType result = field;
가 하는 일은 이미 초기화 된 필드는 딱 한번만 읽도록 함 - 부득이하게 담당클래스를 이용할 수 없거나 재초기화가 필요한 경우 사용
- 처음은 동기화 전, 다음은 동기화 후에 검사해(double check)서 단 한 번만 락이 걸리게 한다.
결론
- 대부분의 필드 초기화는 지연 시키지 않아야 한다.
- 객체 필드 : 이중 검사(double check)숙어 사용
- 정적 필드 : 초기화 지연 담당 클래스(lazy initialization holder class)숙어 사용