Item83. 지연 초기화는 신중히 사용하라
지연 초기화
지연 초기화 (lazy initalization) 는 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 방법이다.
값이 쓰이지 않으면 당연히 초기화도 일어나지 않으며, 지연 초기화는 정적 필드 (static) 와 인스턴스 필드에 모두 사용할 수 있다.
지연 초기화를 함부러 사용하면 안되는게, 객체 생성시 초기화 비용은 최적화가 될 수 있으나 지연 초기화하는 필드에 추가로 접근해야 하는 비용이 생긴다.
지연 초기화를 사용할 것으로 권장되는 상황은 다음과 같다.
- 해당 클래스의 객체 중 그 필드를 사용하는 경우보다 필드를 초기화하는 비용이 더 큰 경우
만약 멀티 쓰레드 환경이라면 지연 초기화하려는 필드가 여러 쓰레드에 의해 공유되는 상황에서는 초기화 작업이 반드시 동기화하고 초기화해야 한다.
위 상황을 제외하고는 대부분의 상황에서는 일반적인 초기화가 지연초기화보다 낫다.
1 | private FieldType field; //지연초기화 방식 |
- 만약 성능떄문에 정적필드를 지연 초기화해야 한다면 lazy initialization holder class 관용구를 사용하자
1 | private static class FieldHolder { |
위처럼 전용 Holder class를 만들어두면 getField()method가 호출될떄 FieldHolder 클래스 초기화가 된다. 이렇게 사용하게 되면 클래스 초기화가 최초에 끝난 뒤에는 아무런 검사나 동기화 없이 필드에 접근하게 된다.
위와 같이 매번 필드에 접근해서 null여부를 확인하지 않아도 된다는 말이다.
- 성능 떄문에 멤버필드를 지연 초기화해야 한다면 double check 관용구를 사용하자
이 방식은 필드의 값을 두 번 검사하는 방식으로 , 최초는 동기화 없이 검사하고 두 번쨰는 동기화하여 검사한다. 두번째 검사에서도 필드가 초기화되지 않았을 때만 필드를 초기화한다. 필드가 초기화된 이후에는 동기화하지 않으므로 해당 필드를 반드시 volatile(메인메모리로부터 저장하고 읽어옴) 로 선언해야 한다.
1 |
|
위 코드에서 result 지역변수를 사용한 이유는 필드가 이미 초기화된 상황에서 필드를 한번만 읽도록 보장하기 위함이다.
Double Check 관용구 - 추가사항
- 가끔 반복해서 초기화해도 상관없는 멤버필드를 지연 초기화할 때가 있는데, 이럴떄에는 이중검사에서 단일검사로 (Single Check) 관용구로 변경해도 된다.
1 |
|
위에서 정리한 내용은 모두 primitive type 과 reference data type에 모두 적용가능하며 단 default 값만 신경써서 지연 초기화를 수행하면 된다(ex) int - 0 , Integer - null )
정리
저자는 지연 초기화는 왠만하면 사용하지 말되, 정말 초기화비용이 비싼경우에 사용하는데 사용할때에는 멀티 쓰레드 환경에서 여러개의 쓰레드가 해당 필드를 동시에 접근해서 초기화를 이상하게 수행할 수 있으니 동기화하여 수행하라고 권고하고 있다.
- 멤버필드 지연 초기화 : Double Check 관용구
- 정적필드 지연 초기화 : Lazy Initialization Holder Class 관용구