# Item 83 - 지연 초기화는 신중히 사용하라

## Intro

> **지연 초기화(lazy initialization)**

* 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
* **용도 및 효과**
  * 지연 초기화는 **최적화 용도**로 쓰인다.
  * 클래스와 인스턴스 초기화 때 발생하는 **위험한 순환 문제**를 해결하는 효과도 있다.
* **적용 대상**
  * 정적 필드
  * 인스턴스 필드

## 지연 초기화

* `클래스` 혹은 `인스턴스 생성` 시의 **초기화 비용**은 줄지만, 지연 초기화하는 **필드에 접근하는 비용**은 커진다.
* `지연 초기화하려는 필드`들 중 결국 **초기화가 이뤄지는 비율**에 따라, **실제 초기화에 드는 비용**에 따라, **초기화된 각 필드를 얼마나 빈번히 호출**하느냐에 따라 `지연 초기화`가 실제로는 **성능을 느려지게 할 수도 있다.**

## 지연 초기화가 필요한 경우

* 해당 클래스의 인스턴스 중 **그 필드를 사용하는 인스턴스의 비율이 낮은 반면**, 그 **필드를 초기화하는 비용이 크다면** `지연 초기화`가 제 역할을 할 것이다.
* 하지만 위 효과를 확인하기 위해서는 `지연 초기화 작용 전후의 성능을 측정`해보는 것이다.

## 멀티 스레드 환경의 지연 초기화

* 멀티스레드 환경에서는 지연 초기화를 하기 힘들다.
* 지연 초기화하는 필드를 둘 이상의 스레드가 공유한다면 반드시 동기화를 해야 한다. [아이템 78](https://seokrae.gitbook.io/sr/book/effective/item_83)

## Thread Safe 한 지연 초기화

* 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다.

> **관용구 1: 인스턴스 필드를 선언할 때의 일반적인 초기화**

```java
class Example {
    // final 한정자를 통한 인스턴스 필드 생성
    private final FieldType field = computeFieldValue();
}
```

> **관용구 2: 인스턴스 필드의 지연 초기화 (synchronized 접근자를 통한 방식)**

* `지연 초기화`가 **초기화 순환성(initialization circularity)** 을 깨뜨릴 것 같으면 **synchronized**를 단 접근자를 사용한다.

```java
class Example {
    private final FieldType field;

    private synchronized FieldType getField() {
        if (field == null) {
            field = computeFieldValue();
        }
        return field;
    }
}
```

* 두 관용구(`보통의 초기화`와 `synchronized 접근자를 사용한 지연 초기화`)는 **정적 필드**에도 똑같이 적용된다.
* 이때 `필드`와 `접근자 메서드 선언`에 **static 한정자**를 추가해야 한다.

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

* 성능면에서 `정적 필드의 지연 초기화`가 필요한 경우 **지연 초기화 홀더 클래스 관용구(lazy initialization holder class)** 를 사용한다.
* 클래스는 `클래스가 처음 쓰일 때 비로소 초기화된다는 특성`을 이용한 관용구

```java
class Example {
    private static class FieldHolder {
        static final FieldType field = computeFieldValue();
    }

    private static FieldType getField() {
        return FieldHolder.field;
    }
}
```

* getField가 처음 호출되는 순간 FieldHolder.field가 처음 읽히면서, 비로소 FieldHolder 클래스 초기화를 촉발한다.
* getField 메서드가 필드에 접근하면서 동기화를 전혀 하지 않으니 성능이 느려질 거리가 전혀 없다.

> **성능이 느려질 이유가 없다?**

* 일반적인 VM은 오직 클래스를 초기화할 때만 필드 접근을 동기화할 것이다.
* 클래스 초기화가 끝난 후에는 VM이 동기화 코드를 제거하여, 그 다음부터는 아무런 검사나 동기화 없이 필드에 접근하게 된다.

> **관용구 4: 성능적인 측면에서의 인스턴스 필드 지연 초기화를 위한 이중검사(double-check)**

* 이 방법은 초기화된 필드에 접근할 때의 동기화 비용을 없애준다.[아이템 79](https://seokrae.gitbook.io/sr/book/effective/item_83)
* **동작 방식**
  * 필드의 값을 두 번 검사하는 방식
  * **한 번**은 동기화 없이 검사
  * **두 번째**는 동기화하여 검사
  * 두 번째 검사에서도 `필드가 초기화되지 않았을 때`만 **필드를 초기화** 한다.
* **주의 사항**
  * 필드가 초기화된 후로는 동기화하지 않으므로 해당 필드는 반드시 volatile 로 선언해야 한다. [아이템 78](https://seokrae.gitbook.io/sr/book/effective/item_83)

```java
class Example {
    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라는 지역변수의 용도는 필드가 이미 초기화된 상황(일반적인 상황에서)에서는 그 필드를 딱 한번만 읽도록 보장하는 역할을 한다.
  * 반드시 필요하지는 않지만 성능을 높여주고, 저수준 동시성 프로그래밍에 표준적으로 적용되는 더 우아한 방법이다.
* **정적 필드**에 대한 **이중검사 관용구** 적용 가능성
  * **정적 필드**를 `지연 초기화`하기 위해서는 **이중검사**보다 **지연 초기화 홀더 클래스** 방식이 더 낫다.

## 이중 검사의 특이 유형 2가지

* 상황
  * `반복해서 초기화해도 상관없는` **인스턴스 필드를 지연초기화**해야 하는 경우, `이중검사`에서 **두 번째 검사를 생략**할 수 있다.

> **이중 검사의 특이 유형 1: 단일 검사(single-check)**

* 필드는 **volatile** 로 선언
* 초기화가 중복해서 일어날 수 있다.

```java
class Example {
    private volatile FieldType field;

    private FieldType getField() {
        FieldType result = field;

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

> **이중 검사의 특이 유형 2: 짜릿한 단일검사(racy single-check)**

* **사용 조건**
  * 모든 스레드가 필드의 값을 다시 계산해도 되고, 필드의 타입이 long과 double을 제외한 다른 기본 타입이라면, 단일검사의 필드 선언에서 volatile 한정자를 없애도 된다.
* **효과**
  * 어떤 환경에서는 필드 접근 속도를 높여주지만, 초기화가 스레드당 최대 한 번 더 이루어질 수 있다.
* 아주 이례적인 기법으로, 보통은 쓰이지 않는다.

## 정리

* 여기서 다룬 모든 초기화 기법은 **기본 타입 필드**와 **객체 참조 필드**에 모두 적용할 수 있다.
* **이중검사**와 **단일검사 관용구**를 **수치 기본 타입 필드**에 적용한다면 필드의 값을 **null 대신**(숫자 기본 타입 변수의 기본값인)**0**과 비교하면 된다.
* 대부분의 필드는 지연시키지말고 곧바로 초기화해야 한다.
* 성능 때문에 혹은 위험한 초기화 순환을 막기 위해 꼭 지연 초기화를 써야 하는 경우 올바른 지연 초기화 기법을 사용한다.
* **인스턴스 필드**에는 이중검사 관용구, **정적 필드**에는 지연 초기화 홀더 클래스 관용구를 사용한다.
* 반복해 초기화해도 괜찮은 인스턴스 필드에는 **단일검사 관용구**도 고려대상이다.
