[배경]
지연 초기화(lazy initialization): 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
- 주로 최적화 용도로 쓰이지만, 클래스와 인스턴스 초기화 때 발생하는 위험한 순환 문제를 해결하는 효과도 있다.
[Why]
지연 초기화는 양날의 검이다.
- 클래스 혹은 인스턴스 생성 시의 초기화 비용은 줄지만 그 대신 지연 초기화하는 필드에 접근하는 비용은 커진다.
- 지연 초기화하려는 필드들 중 결국 초기화가 이뤄지는 비율에 따라, 실제 초기화에 드는 비용에 따라, 초기화된 각 필드를 얼마나 빈번히 호출하느냐에 따라 지연 초기화가 실제로는 성능을 느려지게 할 수도 있다.
지연 초기화가 필요할 때가 있다.
- 해당 클래스의 인스턴스 중 그 필드를 사용하는 인스턴스의 비율이 낮은 반면, 그 필드를 초기화하는 비용이 크다면 지연 초기화가 제 역할을 해줄 것이다.
멀티스레드 환경에서는 지연 초기화를 하기가 까다롭다.
- 지연 초기화하는 필드를 둘 이상의 스레드가 공유한다면 어떤 형태로든 반드시 동기화해야 한다.
[How]
"필요할 때까지는 하지 말라"
- 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다.
지연 초기화가 초기화 순환성(initialization circularity)을 깨뜨릴 것 같으면 synchronized를 단 접근자를 사용하자.
성능 때문에 정적 필드를 지연 초기화해야 한다면 지연 초기화 홀더 클래스(lazy initialization holder class) 관용구를 사용하자.
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
- 이 관용구의 멋진 점은 getField 메서드가 필드에 접근하면서 동기화를 전혀 하지 않으니 성능이 느려질 거리가 전혀 없다는 것이다.
- 일반적인 VM은 오직 클래스를 초기화할 때만 필드 접근을 동기화할 것이다.
- 클래스 초기화가 끝난 후에는 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;
}
}
- 이 관용구는 초기화된 필드에 접근할 때의 동기화 비용을 없애준다(아이템79).
- 필드가 초기화된 후로는 동기화하지 않으므로 해당 필드는 반드시 volatile로 선언해야 한다(아이템78).
- result라는 지역변수는 필드가 이미 초기화된 상황에서 그 필드를 딱 한 번만 읽도록 보장하는 역할을 한다.
이따금 반복해서 초기화해도 상관없는 인스턴스 필드를 지연 초기화해야 할 때가 있는데, 이런 경우라면 이중검사에서 두 번째 검사를 생략할 수 있다. 이 변종의 이름은 자연히 단일검사(single-check) 관용구가 된다.
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result == null) {
field = result = computeFieldValue();
}
return result;
}
모든 스레드가 필드의 값을 다시 계산해도 상관없고 필드의 타입이 long과 double을 제외한 다른 기본 타입이라면, 단일검사의 필드 선언에서 volatile 한정자를 없애도 된다.
- 이 변종은 짜릿한 단일검사(racy single-check) 관용구라 불린다.
- 이 관용구는 어떤 환경에서는 필드 접근 속도를 높여주지만, 초기화가 스레드당 최대 한 번 더 이뤄질 수 있다.
(거의 쓰지 않는다)
'독서찰기(讀書札記) > 이펙티브 자바' 카테고리의 다른 글
[아이템 84] 프로그램의 동작을 스레드 스케줄러에 기대지 말라 (0) | 2022.04.17 |
---|---|
[아이템 82] 스레드 안전성 수준을 문서화하라 (0) | 2022.04.16 |
[아이템 74] 메서드가 던지는 모든 예외를 문서화하라 (0) | 2022.04.06 |
[아이템 73] 추상화 수준에 맞는 예외를 던지라 (0) | 2022.04.05 |
[아이템 72] 표준 예외를 사용하라 (0) | 2022.04.05 |