Design Pattern ‐ Singleton Pattern - dnwls16071/Backend_Summary GitHub Wiki
📚 Singleton Pattern
- 싱글톤 패턴이란 단 하나의 유일한 객체를 만들기 위한 코드 패턴이다.
- 쉽게 말하자면 메모리 절약을 위해, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 만들지 않고 기존의 인스턴스를 가져와 활용하는 기법을 말한다.
- 따라서 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 그 객체가 리소스를 많이 차지하는 역할을 하는 무거운 클래스일 때 적합하다.
Eager Initialization
- 한번만 미리 만들어두는, 가장 직관적이면서도 심플한 기법이다.
static final이라 멀티 쓰레드 환경에서도 안전함- 그러나 static 멤버는 당장 객체를 사용하지 않더라도 메모리에 적재하기 때문에 만일 리소스가 큰 객체일 경우, 공간 자원 낭비가 발생한다.
- 예외 처리를 할 수 없다.
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static final Singleton INSTANCE = new Singleton();
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Static block initialization
- static block 을 이용해 예외를 잡을 수 있다.
- 그러나 여전히 static 의 특성으로 사용하지도 않는데도 공간을 차지함
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
// static 블록을 이용해 예외 처리
static {
try {
instance = new Singleton();
} catch (Exception e) {
throw new RuntimeException("싱글톤 객체 생성 오류");
}
}
public static Singleton getInstance() {
return instance;
}
}
Lazy initialization
- 객체 생성에 대한 관리를 내부적으로 처리한다.
- 메서드를 호출했을 때 인스턴스 변수의 null 유무에 따라 초기화 하거나 있는 걸 반환하는 기법이다.
- 미사용 고정 메모리 차지의 한계점을 극복하지만 쓰레드 세이프(Thread Safe) 하지 않다는 치명적인 단점을 가지고 있다.
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
// 외부에서 정적 메서드를 호출하면 그제서야 초기화 진행 (lazy)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 오직 1개의 객체만 생성
}
return instance;
}
}
synchronized 키워드를 통해 동시성 제어가 가능하나 그만큼 성능 하락이 수반된다는 사실을 염두에 두어야 한다.
Bill Pugh Solution (LazyHolder)
- 싱글톤 패턴을 구현하는 권장되는 두 가지 방법 중 하나이다.
- 멀티쓰레드 환경에서 안전하고 Lazy Loading(나중에 객체 생성)도 가능한 완벽한 싱글톤 기법이다.
- 클래스 안에 내부 클래스(holder)를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법이다.
- static 메소드에서는 static 멤버만을 호출할 수 있기 때문에 치명적인 문제점인 메모리 누수 문제를 해결하기 위하여 내부 클래스를 static으로 설정
- 다만 클라이언트가 임의로 싱글톤을 파괴할 수 있다는 단점을 지니고 있다.(Reflection API, 직렬화/역직렬화)
class Singleton {
private Singleton() {}
// static 내부 클래스를 이용
// Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
private static class SingleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingleInstanceHolder.INSTANCE;
}
}
Enum 이용
- enum은 애초에 멤버를 만들때 private로 만들고 한번만 초기화 하기 때문에 thread safe하다.
- enum 내에서 상수 뿐만 아니라, 변수나 메서드를 선언해 사용이 가능하기 때문에, 이를 이용해 싱글톤 클래스처럼 응용이 가능하다.
- Bill Pugh Solution 기법과 달리, 클라이언트에서 Reflection을 통한 공격에도 안전하다.
- 하지만 만일 싱글톤 클래스를 일반적인 클래스로 마이그레이션 해야할 때 처음부터 코드를 다시 짜야 되는 단점이 존재한다.(개발 스펙은 언제어디서 변경될 수 있기 때문에)
- 클래스 상속이 필요할때, enum 외의 클래스 상속은 불가능하다.
enum SingletonEnum {
INSTANCE;
private final Client dbClient;
SingletonEnum() {
dbClient = Database.getClient();
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public Client getClient() {
return dbClient;
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.getInstance();
singleton.getClient();
}
}