JAVA/Effective Java

규칙 71 - 초기화 지연은 신중하게 하라

반응형

Lazy initialization(초기화 지연)은 필드 초기화를 실제로 그 값이 쓰일 때까지 미루는 것이다.


대부분 초기화 지연의 이유는 초기화의 비용이 증가하고 사용빈도가 특별한 경우에 사용하는 필드에 대해서 그렇게 적용한다.


만약 그렇지 않은 경우에도 초기화 지연을 사용하면 어떨까?

이럴 경우 클래스를 초기화하고 객체를 생성하는 비용은 줄이지만 필드 사용 비용은 증가시킨다.


그럼 동기화가 필요한 다중 스레드 환경에서는 초기화 지연은 어떻게 구현해야할까? 생각만 해도 어렵다. 


몇가지 방법을 살펴보자.

우선 초기화 지연을 사용하지 않고 진행하는 일반적인 초기화는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
 
public class TestClass {
    // 일반적인 초기화 기법
    // 클래스가 처음 로드될 때 바로 초기화
    private final int val = getValue();
    
    private int getValue() {
        return 0;
    }
}
 
cs


이 상태에서 동기화를 적용시키면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
 
public class TestClass {
    // 일반적인 초기화 기법
    // 클래스가 처음 로드될 때 바로 초기화
    private int val;
    
    synchronized int getValue() {
        return 0;
    }
}
 
cs


그렇다면 정적 필드의 초기화를 지연시키고 싶다면 어떻게 해야할까?

이럴때는 Lazy initialization holder class 숙어를 적용하면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestClass {
    
    private static class ValueHolder {
        static final int value = getValue(); 
    }
    
    static int getData() {
        return ValueHolder.value;
    }
    
    private static int getValue() {
        return 0;
    }
}
 
cs


처음 getData()가 호출되는 순간에 getValue()가 호출되면서 초기화가 진행된다. 이럴경우 synchronized를 붙혀주지 않아도된다. 왜냐하면 최신 VM은 클래스를 초기화하기위한 필드 접근은 동기화를 진행한다.


그리고 필드를 검사를 진행할 때 무조건 적으로 동기화 하는 것보다 다음과 같이 이중검사를 사용하는 것이 좋다.


이중검사란 무엇인가? 락을 먼저 걸고 확인하면 무조건 락을 걸어야해서 비용이 증가하지만, 먼저 락없이 한번 확인하고 진행하기 때문에 부담이 줄어든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
String getField() {
        String result = field;
        // 첫 번재 검사 (락 없이 진행)
        if (null == result) {
            // 두 번재 검사  (락을 사용하여 진행)
            synchronized(this) {
                result = field;
                if (result == null) {
                    field = result = getData();
                }
            }
        }
        return result;
    }
    
    String getData() {
        return "dbs";
    }
cs


위 코드에서 result를 사용하여 field값을 대입한 이유는 field 값을 한번만 읽게 하였기에 동기화 사용시에도 비용이 감소된다. 저수준 병렬 프로그래밍에서 25%가량 향상된것을 볼 수있다.


만약 여러번 초기화 되어도 크게 무리가 없는 필드에 경우에는 락을 거는 로직을 빼고 단일 검사만 진행해도 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
String getField() {
    String result = field;
    // 첫 번재 검사 (락 없이 진행)
    if (null == result) {
        field = result = getData();
    }
    return result;
}
    
String getData() {
    return "dbs";
}
cs



결론은 필요없는 필드는 초기화 지연을 사용하지 말자. 비용이 커진다. 만약 비용이 큰 초기화 방법을 적절하게 변경하고 싶은경우 위에 소개한 방식대로 초기화를 진행하라.

반응형