-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
11장동시성동시성
Description
[ Item83 ] 지연 초기화는 신중히 사용하라
지연 초기화
지연 초기화(Lazy Initialization): 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
- 값이 쓰이지 않으면 초기화도 일어나지 않는다
- 정적 필드, 인스턴스 필드 모두에 사용 가능하다
- 주로 최적화 용도로 쓰이고, 클래스와 인스턴스 초기화 때 발생하는 순환 문제를 해결하기도 한다
지연 초기화, 필요할 때까지는 하지 말라
❗️지연 초기화는 양날의 검이다
- 클래스/인스턴스 생성 시의 초기화 비용은 줄지만, 지연 초기화하는 필드에 접근하는 비용은 커진다
- 사용하면 좋은 경우: 해당 클래스의 인스턴스 중 그 필드를 사용하는 인스턴스의 비율은 낮은데, 필드를 초기화하는 비용이 클 경우
- 위의 경우인지 알기 위해서는 지연 초기화 적용 전후의 성능을 측정해봐야한다
멀티스레드 환경
지연 초기화하는 필드를 둘 이상의 스레드가 공유하면 어떤 형태로든 동기화해야 한다.
일단, 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다
private final FieldType field = computeFieldValue();지연 초기화가 반드시 필요한 상황이라고 판단된다면 다음 기법들을 사용할 수 있다. 아래 기법들은 기본 타입 필드와 객체 참조 필드 모두에 적용할 수 있다.
Thread Safe 한 지연 초기화 패턴
1. 지연 초기화를 초기화 순환성(initialization circulatirty)을 깨뜨리는데 사용하는 경우
- synchronized를 단 접근자를 사용하자
- 정적 필드도 필드와 메소드에 static만 추가하면 똑같은 패턴을 쓸 수 있다
private FieldType field;
// synchronized를 사용해 thread safe하게 메소드를 사용할 수 있다
// 필드가 초기화되지 않은 경우만 computeFieldValue()로 초기화한다
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}2. 성능을 위해 정적 필드를 지연 초기화해야 할 경우
- 지연 초기화 홀더 클래스(lazy initialization holder class) 패턴을 사용하자
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
// 클래스는 클래스가 처음 쓰일 때 초기화된다!
// getField()가 처음 호출되는 순간 FieldHolder.field가 처음 읽히며, FieldHolder 클래스 초기화가 된다
private static Fieldtype getField() {
return FieldHolder.field;
}- getField 메서드가 필드에 접근하면서 동기화를 하지 않아, 성능이 느려지지 않는다는 장점이 있다
(일반적인 VM은 클래스를 초기화할 때만 필드 접근을 동기화하고, 끝난 후에는 동기화 코드를 제거하여 그 다음부터는 검사나 동기화 없이 필드에 접근한다)
3. 성능 때문에 인스턴스 필드를 지연 초기화해야 할 경우
- 이중검사(double-check) 패턴을 사용하라
- 필드의 값을 두 번 검사하는데, 필드가 아직 초기화되지 않았다면 동기화 없이 검사하고, 두 번째는 동기화하여 검사한다. 두 번째 검사에서도 필드가 초기화되지 않았을 때만 필드를 초기화환다.
- 초기화된 필드에 접근할 때의 동기화 비용을 없애준다
- 수치 기본 타입 필드에 적용하면 null 대신 0과 비교
// 필드가 초기화된 후에는 동기화하지 않으므로 volatile 선언
private volatile FieldType field;
private FieldType getField() {
// result 지역변수를 사용하면 필드가 이미 초기화된 상황에서는 필드를 딱 한 번만 읽도록 보장할 수 있다
FieldType result = field;
// 첫번째 검사 (동기화 없이)
if (result != null) {
return result;
}
// 두번째 검사 (동기화 사용)
synchronized(this) {
if (field == null) {
field = computeFieldValue();
}
return field;
}
}4. 반복해서 초기화해도 상관없는 인스턴스 필드를 지연 초기화해야 할 경우
- 단일 검사 패턴(single-check)
- 3번에서 두 번째 검사를 생략할 수 있다.
// 필드가 초기화된 후에는 동기화하지 않으므로 volatile 선언
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result == null) {
field = result = computeFieldValue();
}
return result;
}5. 모든 스레드가 필드의 값을 다시 계산해도 상관없고 필드의 타입이 long과 double을 제외한 다른 기본 타입일 경우
- volatile 한정자를 없애도 된다
- racy single-check 패턴이라고 불린다
- 어떤 환경에서는 필드 접근 속도를 높여주지만 초기화가 스레드당 최대 한 번 더 이루어질 수 있다. 보통은 거의 쓰지 않는 패턴이다.
핵심 정리
- 대부분의 필드는 지연시키지 말고 곧바로 초기화하라
- 성능 혹은 초기화 순환을 막기 위해 지연 초기화를 써야 한다면, 위에서 설명한 경우에 따라 올바른 패턴을 사용하자
Metadata
Metadata
Assignees
Labels
11장동시성동시성