Singleton Pattern
인스턴스를 단 한 개만 제공하는 클래스
구현 방법
1. private 생성자와 static 메소드를 이용한 구현
public class Singleton {
// 클래스 당 하나만 존재해야하기 때문에 static 필드를 가지도록 한다.
private static Singleton instance;
// 생성자의 접근 지정자를 private으로 지정함으로써, new를 이용한 생성을 막는다.
private Singleton() {}
// 생성자를 사용할 수 없으니, 외부에서 인스턴스를 얻기 위해 static으로 지정한다.
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
이 구현 방법은 Multi Thread 환경에서 안전하지 않다.
만약 여러 개의 스레드에서 동시에 getInstance()를 호출하게 되면 instance가 생성되지 않은 상태이기 때문에 제각기 다른 인스턴스를 생성하게 될 수 있다.
2. 동기화를 사용해 Multi Thread 환경에서 안전한 구현
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 메서드에 동기화를 걸어 Lock을 걸어 멀티 스레드 환경에서 안전하게 만든다.
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized를 사용하는 데는 비용이 들어가게 된다. 싱글톤 객체를 생성한 이후에 객체에 접근하기 위해서는 항상 synchronized가 작동하게 된다. 따라서, 싱글톤 객체를 자주 사용해야 하는 경우에는 성능 저하를 야기시킬 수 있다.
3. 이른 초기화를 이용한 구현
public class Singleton {
// 클래스 로딩 타임에 함께 생성된다.
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
싱글톤 객체의 사용 유무와 관련 없이 클래스가 메모리에 올라갈 때(ex.메서드가 호출될 때) 싱글톤 객체가 생성되므로 해당 인스턴스의 생성 비용이 큰 경우 비효율적일 수 있다.
4. Double Checked Locking
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
synchronized를 메서드가 아닌 메서드 내부에서 사용함으로써 2번에 걸쳐 instance의 생성 유무를 확인한 뒤 객체를 생성하는 방법이다. 인스턴스가 null인 경우, 즉 생성되지 않았을 때에만 동기화가 실행되도록 함으로써 객체가 생성된 이후에는 동기화가 진행되지 않는다. 이에 따라 동기화 비용을 줄일 수 있게 된다.
5. static inner class를 이용한 구현
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
Holder 클래스는 inner class로 선언되었기 때문에 Singleton 클래스가 Class Loader에 의해서 로딩될 때가 아닌, getInstance()가 호출될 때 JVM 메모리에 로드되면서 객체를 생성하게 된다. JVM에 로드될 때 객체가 생성되기 때문에 Multi Thread 환경에서도 안전하다.
6. Enum 타입을 사용한 구현
public enum Singleton {
INSTANCE;
}
reflection과 직렬화 & 역직렬화를 사용하면 싱글톤 패턴을 깨트릴 수 있다. 이를 방지하기 위해서 사용하는 방법이 바로 Enum 타입으로 싱글톤 패턴을 구현하는 것이다. Enum 클래스는 reflection에서 자체적으로 새로운 인스턴스를 생성할 수 없도록 막아뒀다.
다음으로, enum은 Enum 클래스를 상속 받고 있는데, Enum 클래스는 Serializable 클래스를 상속 받고 있어 직렬화와 역직렬화가 가능하다. 하지만 Enum에 정의되어 있는 readObject() 메서드를 보면, 역직렬화를 진행할 수 없다는 메시지와 함께 예외를 던지기 때문에 새로운 인스턴스를 생성할 수 없다.
readObject() 메서드는 역직렬화를 위해 사용되는 메서드인데, enum이 아닌 클래스에서 역직렬화를 막기 위해서는 싱글톤 객체를 반환하도록 readResolve() 메서드를 정의하면 된다. 이 메서드를 정의하면, readObject()를 통해 생성된 객체는 무시되면서 GC 대상이 된다.
참고: 백기선 - 코딩으로 학습하는 GoF의 디자인 패턴 (인프런)
'Design Pattern' 카테고리의 다른 글
Abstract Factory Pattern (0) | 2022.01.17 |
---|---|
Factory Method Pattern (0) | 2022.01.15 |
댓글