Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ Item83 ] 지연 초기화는 신중히 사용하라 #79

Open
SooKim1110 opened this issue Jun 25, 2022 · 0 comments
Open

[ Item83 ] 지연 초기화는 신중히 사용하라 #79

SooKim1110 opened this issue Jun 25, 2022 · 0 comments
Assignees
Labels
11장 동시성

Comments

@SooKim1110
Copy link

[ 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 패턴이라고 불린다
  • 어떤 환경에서는 필드 접근 속도를 높여주지만 초기화가 스레드당 최대 한 번 더 이루어질 수 있다. 보통은 거의 쓰지 않는 패턴이다.

핵심 정리

  • 대부분의 필드는 지연시키지 말고 곧바로 초기화하라
  • 성능 혹은 초기화 순환을 막기 위해 지연 초기화를 써야 한다면, 위에서 설명한 경우에 따라 올바른 패턴을 사용하자
@SooKim1110 SooKim1110 changed the title # [ Item83 ] 지연 초기화는 신중히 사용하라 [ Item83 ] 지연 초기화는 신중히 사용하라 Jun 25, 2022
@SooKim1110 SooKim1110 self-assigned this Jun 25, 2022
@SooKim1110 SooKim1110 added the 11장 동시성 label Jun 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
11장 동시성
Projects
None yet
Development

No branches or pull requests

1 participant