본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Effective Java] 늦 초기화를 분별력 있게 사용하자

by 돼지왕 왕돼지 2017. 3. 16.
반응형

 [Effective Java] 늦 초기화를 분별력 있게 사용하자


1.5, double-check idiom, Effective JAVA, getinstance, holder pattern, initialize ondemand holder class idiom, lazy initialization, lazy initialization holder class idiom, lock cost, racy single-check idiom, single-check idiom, static 필드 성능, synchronized, vm, volatile, [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 필드에는 늦 초기화 홀더 클래스 이디엄이 좋다.
반복 초기화를 해도 괜찮은 인스턴스 필드의 경우에는 단일-검사 이디엄의 사용도 고려해 볼 수 있다.





반응형

댓글