-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
지연 초기화는 신중히 사용하라
지연 초기화
- 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
- 값이 안 쓰이면 초기화도 X
- 정적 필드와 인스턴스 필드 모두에 사용 가능
- 최적화나 클래스&인스턴스 초기화 때 발생하는 순환문제 해결하는 효과
지연 초기화가 필요할 때까지는 하지 말라
- 클래스 또는 인스턴스 생성 시 초기화 비용은 줄지만 필드에 지연초기화하는 필드에 접근하는 비용은 커짐
- 대부분 일반적인 초기화가 낫다
지연 초기화가 필요할 때
- 지연 초기화하려는 필드를 사용하는 인스턴스 비율은 낮은데 초기화 비용은 큰 경우
- 여러 스레드가 지연 초기화 필드를 공유하면 버그 발생 가능 (동기화해야 함)
인스턴스 필드를 초기화하는 일반적인 방법
private final FieldType field = computeFieldValue();인스턴스 필드의 지연 초기화 - synchronized 접근자 방식
지연 초기화가 초기화 순환성(initialization circularity)을 깨뜨릴 것 같으면 synchronized를 단 접근자를 사용하자.
초기화 순환성 깨기 위해 지연초기화를 사용하고 싶다면 synchronized를 사용하라는 것 같음
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}정적 필드용 지연 초기화 홀더 클래스 관용구
지연 초기화 홀더 클래스(lazy initialization holder class) 관용구: 클래스는 클래스가 처음 쓰일 때 비로소 초기화된다는 특성을 이용한 관용구
private static class Fieldholder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field;}- getField 호출
- FieldHolder.field가 처음 읽히면서, FieldHolder 클래스 초기화
- getField() 메서드가 필드에 접근하면서 동기화를 사용하지 않아 성능 저하 우려 X
- 대부분 VM은 클래스를 초기화할 때만 필드 접근을 동기화
- 클래스 초기화가 끝난 후에는 VM이 동기화 코드를 제거하여, 그 다음부터는 아무런 검사나 동기화 없이 필드에 접근
인스턴스 필드 지연 초기화용 이중검사 관용구
- 초기화된 필드에 접근할 때의 동기화 비용을 없애준다.
- 첫 번째는 동기화 없이, 두 번째는 동기화하여 필드의 값을 검사
- 두 번째 검사에서도 초기화 안 된 경우만 필드를 초기화
- 필드 초기화 이후에는 동기화하지 않으므로 volatile로 선언 (java 변수를 cpu cache가 아닌 메인 메모리에 저장)
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result != null) { // 첫 번째 검사 (락 사용 안 함)
return result;
synchronized(this) {
if (field == null) // 두 번째 검사 (락 사용)
field = computeFieldValue();
return field;
}
}- result: 필드가 이미 초기화된 상황에서 해당 필드를 한번만 읽도록 보장하는 역할
단일검사(single-check) 관용구
- 반복해서 초기화해도 상관없는 인스턴스 필드를 지연초기화해야 할 때 사용
- 두 번째 검사 생략
- 필드는 volatile로 선언
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result == null)
field = result = computeFieldValue();
return result;
}짜릿한 단일검사(Racy Single-Check) 관용구
- 모든 스레드가 필드의 값을 다시 계산해도 상관없고 필드의 타입이 long과 double이 아닌 기본 타입이라면, volatile 한정자를 안 써도 된다.
- 언어 명세상 long & double 외의 변수를 읽고 쓰는 동작은 atomic하기 때문
- 64 bit JVM에선 atomic / https://stackoverflow.com/questions/517532/writing-long-and-double-is-not-atomic-in-java
- 잘 안 쓴다.
핵심 정리
- 대부분의 필드는 지연시키지 말고 바로 초기화하자.
- 꼭 지연 초기화를 써야 한다면 인스턴스 필드에는 이중검사 관용구를, 정적 필드에는 홀더 클래스 관용구를 사용하자.
- 반복해 초기화해도 괜찮은 인스턴스 필드에는 단일검사 관용구를 사용해도 된다.