티스토리 뷰

728x90
반응형

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


  • 지연 초기화란 사용할 필드 및 인스턴스의 초기화 시점을 실제로 그 값이 필요로 할때까지 늦추는 기법입니다. 지연 초기화를 사용하면 값이 쓰이지 않는 시점에 굳이 초기화하지 않아도 되며, 클래스와 인스턴스 초기화 때 발생하는 순환참조 문제도 해결할 수 있습니다.
  • 클래스 혹은 인스턴스 생성시의 초기화 비용은 줄어들 수 잇겠지만, 지연 초기화는 필드에 접근하는 비용은 비싸집니다.
  • 지연 초기화하려는 필드들 중 초기화가 이루어지는 비욜, 실제 초기화에 드는 비용 등에 따라 초기화된 각 필드를 얼마나 빈번히 호출하느냐에 따라 지연 초기화는 오히려 성능을 느리게 만들 수 있습니다.

💡 언제 지연 초기화가 필요할까?

  • 필드를 사용하는 빈도가 낮으며, 필드 초기화하는 비용이 비싼 경우입니다.

💡 멀티 스레드 환경에서의 지연 초기화

  • 멀티 스레드 환경에서는 필드에 둘 이상의 스레드가 접근할 수 있게되는데 이런 경우 반드시 동기화가 필요합니다. 지연 초기화가 초기화 순환성을 깨뜨릴 것 같은 경우 synchronized 접근자를 사용하는게 좋습니다.
  • 만을 정적 필드도 지연 초기화를 해야하는 경우 지연 초기화 홀더 클래스(lazy initializaion holder class)를 사용할 수 있습니다.
    자세히 보기(Initializaion on demand holder) 해당 기법을 사용하게 되면 클래스는 클래스가 처음 쓰일 때 비로서 초기화된다는 특성을 이용하였습니다. getField 메서드를 호출하게 되면 그제서야 FieldHolder 클래스가 초기화되며 getField 메서드가 필드에 접근하면서 동기화를 하지 않으니 성능 이슈도 없습니다. 일반적인 VM은 오직 클래스를 초기화할 때만 필드 접근을 동기화할 것입니다. 클래스 초기화가 끝난 후에는 VM이 동기화 코드를 제거하여 그 다음부터는 아무런 검사나 동기화 없이 필드에 접근하게 됩니다.
// 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;
}

 

💡 이중 검사(double-check)

  • 성능 때문에 인스턴스 필드를 지연 초기화해야 한다면 이중검사 관용구를 사용할 수 있습니다.
  • result라는 지역 변수의 용도는 필드가 이미 초기화된 상화엥서 그 필드를 딱 한번만 읽도록 보장하는 역할을 수행합니다. 반드시 필요하지는 않지만 성능을 높여주며, 저수준의 동시성 프로그래밍에서 표준적으로 적용되는 방법입니다. 하지만 정적 필드에 대해서는
    이중 검사보다 지연 초기화 홀더 클래스 방식이 더 낫습니다.
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result != null) {  // 첫 번째 검사(락 사용 안 함)
        return result;
    }
    
    synchronized(this) {
        if (field == null) {  // 두 번째 검사(락 사용)
            field = computeFieldValue();
        }
        return field;
    }
}

 

💡 단일 검사(single-check)

  • 필드를 volatile로 선언하고, 단일 검사방식을 사용하게 되면 하나의 스레드가 초기화하고 있는데 다른 스레드가 비집고 들어오게 되면 초기화가 중복해서 일어날 수 있습니다.
private volatile FieldType field;
 
private FieldType getField() {
    FieldType result = field;

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

 

 

✔️ 정리

  • 이번 아이템에서 다룬 모든 초기화 방법은 기본 타입 필드와 객체 참조 필드에 모두 적용할 수 있습니다.
  • 이중검사와 단일검사 관용구를 수치 기본 타입 필드에 적용한다면 필드의 값을 null 대신 0으로 비교하면 됩니다.
  • 인스턴스 필드에는 이중검사 관용구를 사용하고, 정적 필드에는 지연 초기화 홀더 클래스 관용구를 사용하는게 좋습니다.

 

 

 

 

 

 

728x90
반응형