요약
•
지연 초기화는 성능 개선이 필요한 순간까지 하지 말자.
지연 초기화(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로 선언해야한다.