하나만 있어도 충분한 객체
// NOTE: This is not thread safe!
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
public String getDescription() {
return "I'm a classic Singleton!";
}
}
getInstance()
로 인스턴스를 달라고 요청해야 함싱글턴 패턴(Singleton Pattern)은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공합니다.
ChocolateBoiler biler = ChocolateBoiler.getInstance();
boiler.fill();
boiler.boil();
boiler.dtain();
2개의 스레드에서 이 코드를 실행한다고 가정해 보고, 두 스레드가 다른 보일러 객체를 사용하게 될 가능성은 없는지 따져 봅시다.
정답
getInstance()
를 동기화 하면 멀티스레딩과 관련된 문제가 간단하게 해결됩니다.
// NOTE: This is not thread safe!
public class Singleton {
...
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
...
}
다만 메소드를 동기화하면 성능이 100배 정도 저하된다는 사실만은 기억해둡시다.
public class Singleton {
// 정적 초기화 부분에서 Singleton의 인스턴스를 생성합니다. 이러면 스레드를
private static Singleton uniqueInstance = new Singleton()
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
DCL을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.
이러면 처음에만 동기화 하고 나중에는 동기화하지 않아도 됩니다.
// java 1.4 이전 버전에서는 사용 불가
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
맞습니다. 하지만 필요한 내용이 클래스에 다 들어있고, 복잡한 초기화가 필요 없는 경우에만 그 방법을 쓸 수 있습니다.
다만 자바에서 정적 초기화를 처리하는 방법 때문에 일이 복잡해질 수도 있고 여러 클래스가 얽혀 있다면 꽤 지저분해집니다.
초기화 순서 문제로 찾아내기 어려운 버그가 생길 수도 있으니 특별한 이유가 없다면 쓰지 맙시다.
클래스 로더마다 서로 다른 네임스페이스를 정의하기에 2개 이상이라면 같은 클래스를 여러번 로딩할 수도 있습니다.
따라서 클래스 로더가 여러 개라면 싱글턴을 조심해서 사용해야 합니다.
역시 싱글턴에서 문제가 될 수 있으므로 염두에 둘 필요가 있습니다.
맞습니다. 싱글턴을 바꾸면 연결된 모든 객체도 바꿔야 할 가능성이 높으니까요.
맞습니다. 싱글턴은 자신의 인스턴스를 관리하는 일 외에도 원래 그 인스턴스를 사용하고자 하는 목적에 부합하는 작업을 책임져야 합니다.
따라서 2가지를 책임지고 있다고 말할 수도 있습니다. 하지만 클래스 내에 인스턴스 관리 기능을 포함한 클래스를 적지 않게 볼 수 있습니다. 그러면 전체적인 디자인을 더 간단하게 만들 수 있기 때문입니다.
전역 변수는 게으른 인스턴스 생성을 할 수 없고, 처음부터 끝까지 인스턴스를 가지고 있어야 한다는 단점이 있습니다.
또한 간단한 객체의 전역 레퍼런스를 자꾸 만들게 되어 네임스페이스 역시 지저분하게 만들어집니다. 싱글턴은 이런 경우는 거의 없습니다.
지금까지 논의한 동기화 문제, 클래스 로딩 문제, 리플렉션, 직렬화와 역직렬화 문제 등은 enum으로 싱글턴을 생성해서 해결 할 수 있습니다.
public enum Singleton {
UNIQUE_INSTANCE;
// 기타 필요한 필드
}
public class SingletonClient {
public static void main(String[] args) {
Singleton singleton = Singleton.UNIQUE_INSTANCE;
System.out.println(singleton.getDescription());
}
}
이제 싱글턴이 필요할 때면 바로 enum을 쓰면 됩니다.
객체지향 원칙
싱글턴 패턴