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

아이템 83. 지연 초기화는 신중히 사용하라 #85

Open
HanaHww2 opened this issue Apr 25, 2023 · 1 comment
Open

아이템 83. 지연 초기화는 신중히 사용하라 #85

HanaHww2 opened this issue Apr 25, 2023 · 1 comment
Assignees

Comments

@HanaHww2
Copy link
Contributor

No description provided.

@yeGenieee
Copy link
Collaborator

[83] 지연 초기화는 신중히 사용하라

지연 초기화

  • 필드의 초기화 시점을 그 값이 처음 필요할 때 까지 늦추는 기법
    • 값이 전혀 쓰이지 않으면, 초기화도 결코 일어나지 않게 된다
  • 왜 사용?
    • 최적화 용도로 사용되며, 클래스와 인스턴스 초기화 시 발생하는 위험한 순환 문제를 해결하는 효과도 있다

지연 초기화 특징

  • 인스턴스 생성 시 초기화 비용은 줄지만, 그 필드에 접근하는 비용이 커진다
  • 따라서, 초기화가 이루어지는 비율, 실제 초기화에 드는 비용, 초기화 된 각 필드의 호출 빈도에 따라 성능이 느려질 수 있다
  • 따라서, 진짜 지연 초기화가 필요할 때 까지 사용하지 말자

초기화 방법

1. 일반적인 초기화 방법

private final FieldType field = computeFieldValue();
  • 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다

2. 지연 초기화 - synchronized 한정자

  • 지연 초기화가 초기화 순환성 (initialization circularity) 을 깨뜨릴 것 같으면 synchronized를 단 접근자를 사용해라

초기화 순환성

  • Class A의 생성자가 Class B의 인스턴스를 생성하고,
  • Class B가 Class C의 인스턴스를 생성하고,
  • Class C가 Class A의 인스턴스를 생성하는 경우
private FieldType field;
private synchronized FieldType getField() {
    if (field == null) {
        field = computeFieldValue();
    } 
    return field;
}

3. 정적 필드 지연 초기화 홀더 클래스 관용구

  • 성능때문에 정적 필드를 지연 초기화해야 한다면, 지연 초기화 홀더 클래스(lazy initialization holder class) 관용구를 사용하자
  • 클래스는 클래스가 처음 쓰일 때 비로소 초기화 된다는 특성이 있는데, 이를 이용한 관용구이다
private static class FieldHolder {
    static final FieldType field = computeFieldValue();
}

private static FieldType getField() {
    return FieldHolder.field;
}
  • getField() 메서드가 호출되면, FieldHolder.field 가 읽히면서 FieldHolder 클래스 초기화를 한다
  • 동기화를 하지 않기 때문에 성능이 느려질 걱정이 없다는 장점을 가진다

4. 인스턴스 필드 지연 초기화 이중검사 관용구

  • 성능 때문에 인스턴스 필드를 지연 초기화해야 한다면, 이중검사 관용구를 사용하자
  • 초기화된 필드에 접근할 때의 동기화 비용을 없애준다
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;

    // 첫번째 검사 (Lock 사용 안함)
    if (result != null) {
        return result;
    }

    // 두번째 검사 (Lock 사용)
    synchronized(this) {
        if (field == null) {
            field = computeFieldValue();
        }
        return field;
    }
}
  • 한 번은 동기화 없이 검사하고, 두 번째 검사는 동기화하여 검사한다
  • 두번째 검사에서도 필드가 초기화되지 않았을 때만 필드를 초기화한다
  • 필드가 초기화 된 후로는 동기화하지 않으므로 해당 필드는 반드시 volatile로 선언해야 한다

volatile

  • java 변수를 메인 메모리에 저장하겠다고 명시하는 키워드
  • 매번 변수의 값을 read 할 때 마다 CPU cache에 저장된 값이 아닌, 메인 메모리에서 읽는다
  • 변수의 값을 write할 때 마다 메인 메모리에까지 작성한다
  • volatile 변수를 사용하지 않는 멀티스레드 어플리케이션에서는 태스크를 수행하는 동안 성능 향상을 위해 메인 메모리에서 읽은 변수 값을 CPU cache 에 저장한다
  • 만약, 멀티스레드 환경에서 스레드가 변수 값을 읽어올 때, 각 CPU cache에 저장된 값이 다르기 때문에 변수 값 불일치 문제가 발생하게 된다
  • volatile 키워드를 통해 멀티 스레드 환경에서 하나의 스레드만 read & write하고, 나머지 스레드가 read하는 상황에서 가장 최신의 값을 보장한다
  • CPU cache보다 메인 메모리가 비용이 더 크기 때문에 변수 값 일치를 보장해야 하는 경우에만 volatile을 사용하는 것이 좋다

5. 이중검사 관용구의 변종 - 단일 검사 관용구

  • 이중 검사 관용구에서 반복해서 초기화해도 상관없는 인스턴스 필드를 지연 초기화할 때 초기화가 중복해서 일어날 수 있다
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;

    if (result == null) {
        field = result = computeFieldValue();
    } 
    return result;
}

6. 이중검사 관용구의 변종 - 짜릿한 단일 검사 관용구 (racy single-check)

  • 모든 스레드가 필드의 값을 다시 계산해도 상관없고, 필드의 타입이 long과 double을 제외한 다른 기본 타입이라면, 필드 선업에서 volatile 한정자를 없애도 된다
private FieldType field;

private FieldType getField() {
    FieldType result = field;

    if (result == null) {
        field = result = computerFieldValue();
    }
    return result;
}
  • 이 관용구는 필드의 접근 속도를 높여주지만, 초기화가 스레드 당 최대 한 번 더 이뤄질 수 있다
  • 이례적인 기법으로, 거의 사용되지 않는다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants