Singletons and lazy loading
싱글톤 패턴이 클래스 달랑 하나여서 제일 간단해 보이는 디자인 패턴임에도 불구하고 Lazy Loading 또는 초기화 지연 기법을 사용하려고 하면 굉장히 복잡해 지네요. 그리고 동기화 문제도 생각하면 syncronized가 필수가 됩니다. 그래서 보통은 아래 처럼 구현을 하죠.
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
return (instance != null) ? instance : (instance = new Singleton())
}
}
하니면 초기화 지연 기법을 포기하고 아예 처음 변수를 선언할 때 객체도 만들어 버립니다.
private static Singleton instance = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance() {
return instance;
}
}
아니면 Head First Design Pattern에도 살짝 나왔던 것 같은데 2중 체크 라는 것을 합니다. 하지만 이것도 되는 것 처럼 보일 뿐 제대로 동작하지 않습니다.
class Foo {
private int cachedHashCode = 0;
public int hashCode() {
int h = cachedHashCode;
if (h == 0)
synchronized(this) {
if (cachedHashCode != 0) return cachedHashCode;
h = computeHashCode();
cachedHashCode = h;
}
return h;
}
// other functions and members...
}
예제 코드가 좀 거시기 하지만 얼추 이와 비슷한 코드들이 동기화 관련된 부분에서 자주 보입니다. 이게 제대로 동작하지 않는 이유는 빨간 색 코드를 호출해서 h가 참조할 객체가 만들어 지는 도중에 녹색 조건 문에 다른 쓰레드가 걸려서 다 만들어 지지도 않은 객체의 레퍼런스를 가져갈 수 있기 때문인 것 같은데 이 것에 대한 자세한 내용은 여기를 참조하세요.
Anyway... 그럼 어떻게 하면 좀더 간단하고 빠르게(syncronized를 쓰지 않고) 싱글톤과 초기화 지연 기법을 사용할 수 있을까 하는게 참조한 글의 주제 였는데요.
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
이 코드는 위키피디아에 Initialization on Demand Holder를 참조 했다고 합니다. 위 코드를 보시면 먼저 Syncronized가 사라진걸 볼 수 있구요. 따라서 멀티 쓰레드 환경에서 매우 빠르게 동작할 것 같습니다. 그리고 초기화 지연은 어떻게 보장을 할까요. getInstance() 메소드가 호출되면 그 때 static inner 클래스인 SingletonHolder가 JVM에 로딩 되게 되고 그 때서야 객체는 만들어 지게 되는거죠. 그리고 그 이후에는 static이니까 싱글톤.. 단일 객체를 보장하게 되는 겁니다.