Singleton pattern
하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.
즉, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 생성하지 않고 기존의 인스턴스를 활용하는 기법이다.
장점 : 인스턴스 생성 시 비용이 줄어듦
단점 : 의존성이 높아짐
싱글톤 패턴을 구현하는 기법에는 총 7가지가 있으며, 각 기법마다 장단점이 존재해 순서대로 조금씩 단점을 보완하는 식으로 발전해왔다.
1. Eager Initialization
static final이라 멀티 쓰레드 환경에서도 안전static 멤버는 객체를 사용하지 않더라도 메모리에 적재되기 때문에 리소스가 큰 객체일 경우 공간의 자원 낭비가 발생class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static final Singleton INSTANCE = new Singleton();
// 생성자를 private으로 사용하여 외부에서 new 사용을 막음
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
2. Static Block Initialization
static block을 이용해 예외 처리 가능static block? : 클래스가 로딩되고 클래스 변수가 준비된 후 자동으로 실행되는 블록static의 특성인 공간 낭비가 있음class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private으로 사용하여 외부에서 new 사용을 막음
private Singleton() {}
// static 블록을 이용해 예외 처리
static {
try {
instance = new Singleton();
} catch (Exception e) {
throw new RuntimeException("싱글톤 객체 생성 오류");
}
}
public static Singleton getInstance() {
return instance;
}
}
3. Lazy Initialization
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private으로 사용하여 외부에서 new 사용을 막음
private Singleton() {}
// 외부에서 정적 메서드 호출 시 초기화 진행 (lazy)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 오직 1개의 객체만 생성
}
return instance;
}
}
4. Thread Safe Initialization
synchronized 키워드를 통해 메서드에 쓰레드들을 하나씩 접근하게 하도록 설정 (동기화)synchronized 메서드를 호출하여 동기화 처리 작업에 overhead가 발생, 성능이 하락한다.class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 메서드
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
5. Double-Checked Locking
synchronized를 적용하고 이미 만들어진 인스턴스를 반환할 때에는 사용하지 않도록 하는 기법volatile 키워드를 붙여주어야 I/O 불일치 문제를 해결 할 수 있다.volatile 키워드를 이용하려면 JVM 1.5 이상이어야 하며, JVM에 대한 심층적 이해를 요구하고, JVM에 따라서 여전히 쓰레드 세이프 하지 않는 경우가 발생하기 때문에 사용을 지양하는 편class Singleton {
private static volatile Singleton instance; // volatile 키워드 적용
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
// 메서드에 동기화를 적용하지 않고, Singleton 클래스에 동기화 적용
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton(); // 최초 초기화 할 때만 동기화 작업이 발생, 리소스 낭비를 최소화
}
}
}
return instance; // 최초 초기화가 완료되면 앞으로 생성된 인스턴스만 반환
}
}
6. Bill Pugh Solution (Lazy Holder)
static 메소드에서는 static 멤버만 호출 가능한 점과, 내부 클래스의 메모리 누수라는 치명적인 문제점 해결을 위해 내부 클래스를 static으로 설정 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;
}
}
7. Enum 이용
enum은 멤버 생성 시 private로 만들고 한 번만 초기화 하기 때문에 쓰레드 세이프 하다.enum 내에서 상수 뿐 아니라 변수나 메서드를 선언해 사용 가능하기 때문에 싱글톤 클래스처럼 응용 가능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();
}
}
따라서, 싱글톤 패턴 클래스를 만들고자 한다면 Bill Pugh Solution 기법 혹은 Enum 이용을 권장한다.
성능이 중요시 되는 환경이라면 Bill Pugh Solution 기법을, 직렬화 및 안정성이 중요시 되는 환경이라면 Enum을 사용하자!