[디자인 패턴] 싱글톤 패턴 (Singleton pattern)
싱글톤 패턴이란?
인스턴스를 매번 생성하지 않고 최초에 생성한 객체를 공유하여 사용하는 패턴이다. 인스턴스 한 개로 여러 요청을 처리할 수 있다면 메모리상에서 효율적이며 객체의 추가적인 생성이 없으므로 성능이 빠르다. 하지만 하나의 객체를 공유한다는 점에 있어서 thread safe여부를 주의해야한다.
일반적으로 싱글톤 패턴은 생성자를 private로 선언하여 new키워드로 객체의 생성을 막고 정적 팩터리 메서드형태로 인스턴스를 반환한다.
방법1. synchronized method
public class RemoteControl {
private static RemoteControl remoteControl;
private RemoteControl() {
}
public static synchronized RemoteControl getInstance() {
if(remoteControl == null){
remoteControl = new RemoteControl();
}
return remoteControl;
}
}
정적 팩터리 메서드에 synchronized키워드를 붙여서 객체를 반환하는 방법이다. 단일 스레드 환경이라면 synchronized키워드가 불필요하지만, 여러 스레드에서 동시에 getInstance()메서드를 호출하고 객체가 아직 만들어지기 전이라면 여러 객체가 동시에 생성될 가능성이 있다. thread safe를 보장하지만 synchronized키워드를 사용하므로서 객체에 lock이 걸리고 성능이 저하된다는 단점이 있다.
방법2. synchronized block
public class RemoteControl {
private static RemoteControl remoteControl;
private RemoteControl() {
}
public static RemoteControl getInstance() {
if(remoteControl == null){
synchronized (RemoteControl.class){
remoteControl = new RemoteControl();
}
}
return remoteControl;
}
}
두 번째 방법은 첫 번째와 lock을 거는 방식이 다르다. 메서드에 synchronized키워드를 사용하면 객체에 lock을 건다. remoteControl객체가 null이건 아니건 매번 getInstance()메서드에 접근할 때마다 lock건다는 것이다. 객체가 여러번 생성되는 것을 막기위해 lock을 걸지만, 객체가 이미 생성되어있는데도 lock을 거는 것은 비효율적이다. 따라서 synchronized를 block으로 사용하여 객체가 생성되지 않는 상황에서 객체 생성에 대한 코드만 lock을 거는 편이 성능상 이점이 있다.
방법 3. 클래스 로딩 시점에서 인스턴스 생성
public class RemoteControl {
private final static RemoteControl remoteControl = new RemoteControl();
private RemoteControl() {
}
public static RemoteControl getInstance() {
return remoteControl;
}
}
위 코드는 클래스가 로딩되는 시점에서 객체가 생성된다. 이후 클라이언트는 getInstance()메서드를 통해 객체에 접근하기 때문에 thread safe하다.
만약 클래스에서 getInstance()이외에 다른 static 메서드를 사용하는 경우라면 굳이 인스턴스를 미리 메모리에 올릴 필요는 없다. 사용하는 시점에서 올리고 싶다면 아래와 같이 lazy holder방식으로도 사용이 가능하다.
public class RemoteControl {
private RemoteControl() {
}
private static class Instance{
private static RemoteControl remoteControl = new RemoteControl();
}
public static RemoteControl getInstance() {
return Instance.remoteControl;
}
}
방법 4. Enum을 사용한 싱글톤
public enum RemoteControl {
INSTANCE;
}
간결하고 추가적인 노력없이 직렬화가 가능하다. 또, 아주 복잡한 직렬화 상황이나 리플렉션 공격에도 제2의 인스턴스가 생기는 일을 완벽히 막아준다(enum내부에 clone()은 final로 정의되어 있음)