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();
    }
}