[Effective Java] 늦 초기화를 분별력 있게 사용하자 |
-
늦 초기화는 양날의 검이다.
클래스를 초기화하거나 인스턴스를 생성하는 비용은 줄여주지만, 그 대신 늦게 초기화되는 필드의 접근 비용은 증가시킨다.
늦 초기화는 실제로 성능을 저하시킬 수 있다. ( 다른 많은 최적화 처럼 )
-
늦 초기화는 나름의 용도가 있다.
만일 어떤 필드가 어떤 클래스 인스턴스의 일부로만 사용되고, 그러면서 그 필드의 초기화 비용이 많이 든다면 늦 초기화가 좋을 수 있다.
확실히 하기 위해서는 역시나 성능을 측정하는 것이 좋겠다.
-
다중 스레드의 경우에는 늦 초기화가 쉽지 않다.
만일 두 개 이상의 스레드가 늦게 초기화되는 필드를 공유한다면, 어떤 형태로든 동기화 하는 것이 중요하며,
동기화를 하지 않으면 심각한 결함을 초래할 수 있다.
-
대부분의 상황에서는 정상적인 초기화가 늦 초기화보다 좋다.
private final SomeClass someClass = init();
-
만일 초기화 순환성을 막기 위해 늦 초기화를 사용한다면,
synchronized 키워드를 사용하자.
private SomeClass someClass;
synchronized SomeClass getInstance(){
if ( someClass == null ){
someClass = init();
}
return someClass;
}
-
만일 static 필드의 성능을 고려해서 늦 초기화를 사용할 필요가 있다면,
늦 초기화 홀더 클래스 이디엄(lazy initialization holder class idiom)을 사용하자.
이 이디엄은 초기화 요구 홀더 클래스 이디엄(initialize ondemand holder class idiom) 이라고도 한다.
private static class SomeClassHolder{
static final SomeClass someClass = init();
}
static SomeClass getInstance(){
return SomeClassHolder.someClass;
}
이 녀석은 getInstance 가 최초 호출될 때, 최초로 SomeClassHolder.someClass 를 읽는다.
그로 인해 SomeClassHolder 가 초기화 된다.
이 이디엄의 장점은 getInstance 메소드가 동기화되지 않아서 필드 접근만을 수행한다는 것이다.
근래의 VM 은 클래스를 초기화하는 필드의 접근만 동기화시킨다.
일단 클래스가 초기화되면, 그 이후에 그 필드를 접근할 때는 어떤 테스트 코드나 동기화 코드가 포함되지 않도록 VM 이 코드를 조정한다.
-
만일 인스턴스 필드의 성능을 고려해서 늦 초기화를 사용할 필요가 있다면, 이중-검사 이디엄(double-check idiom) 을 사용하자.
이 이디엄은 필드가 초기화된 후 사용될 때 락(lock) 비용 발생을 막아준다.
이 이디엄의 아이디어는 해당 필드의 값을 두 번 검사하는 것이다.
일단 락을 안 걸고 한 번 초기화한 다음, 만일 그 필드가 초기화되지 않았으면, 두 번째는 락을 걸고 초기화한다.
필드가 이미 초기화 되었다면 락을 걸지 않으므로, 그 필드를 volatile 로 선언하는 것이 중요하다.
private volatile SomeClass someClass;
SomeClass getInstance(){
SomeClass result = someClass;
if ( result == null ){
synchronized( this ){
result = someClass;
if ( result == null )
someClass = result = init();
}
}
return result;
}
자바 1.5 배포판 이전에는 이중-검사 이디엄이 신뢰도 있게 실행되지 않았다.
volatile 변경자의 의미가 그것을 제대로 지원할 만큼 충분히 강하지 않았기 때문이다.
1.5 배포판에서 소개된 메모리 모델에서는 이 문제를 해결하였다.
-
이중-검사 이디엄보다는 holder pattern 이 더 좋지만,
이중-검사 이디엄도 쓰일 곳이 있긴 있다.
반복적인 초기화를 할 경우가 있을 경우이다.
만일 코드가 너무 복잡하고, 여러번 초기화되도 큰 상관이 없는 경우에는 단일-검사 이디엄(single-check idiom)을 적용할 수 있다.
private volatile SomeClass someClass;
private SomeClass getInstance(){
SomeClass result = someClass;
if ( result == null ){
someClass = result = init();
}
return result;
}
-
레이시 단일-검사 이디엄(racy single-check idiom) 도 있는데 이에 대한 설명은 스킵한다.
Summary
대부분의 필드는 늦 초기화가 아닌 정상적인 초기화를 해야 한다.
원하는 수준의 성능을 얻기 위해, 또는 해로운 초기화 순환성을 막기 위해 필드를 늦게 초기화해야 한다면, 그 상황에 적합한 늦 초기화 방법을 사용하자.
인스턴스 필드의 경우에는 이중-검사 이디엄이 적합하며, static 필드에는 늦 초기화 홀더 클래스 이디엄이 좋다.
반복 초기화를 해도 괜찮은 인스턴스 필드의 경우에는 단일-검사 이디엄의 사용도 고려해 볼 수 있다.
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[Effective Java] 스레드 그룹을 사용하지 말자. (0) | 2017.03.20 |
---|---|
[Effective Java] 스레드 스케쥴러에 의존하지 말자 (0) | 2017.03.17 |
[Effective Java] 스레드 안전을 문서화 하자. (0) | 2017.03.14 |
[Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자. (0) | 2017.03.13 |
[Effective Java] 스레드 그룹보다는 실행자와 작업을 사용하자. (0) | 2017.03.10 |
댓글