본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

Singleton

by 민휘 2023. 2. 24.

핵심 의도

싱글턴 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

 

적용 상황

프로그램이 실행될 때 인스턴스를 하나만 만드는 것이 더 안전하거나 경제적인 경우 사용한다. 예를 들어 스레드 풀이나 커넥션 풀, 캐시, 대화상자, 설정, 로그, 디바이스 드라이버 등이 있다. 또한 하나만 존재해야하는 객체의 생성 비용이 큰 경우 지연생성 방식으로 필요할 때 인스턴스를 생성하는 상황에 적합하다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

객체는 원래 맡은 책임과 함께 자신이 하나만 존재하도록 관리하고 생성하는 책임을 담당한다. 따라서 싱글톤 객체는 외부로부터 생성 요청을 받아서 자신의 인스턴스를 생성한다. 싱글톤 객체는 자신만 접근할 수 있는 private 생성자를 가지고, 외부로부터 생성 요청을 받는 public 메소드를 가진다.

 

구현 포인트

실행 중에 인스턴스를 하나만 존재하도록 보장하는 것이 생각보다 어렵다. 멀티스레드, 직렬화, 리플렉션 등의 변수 가능성이 있기 때문이다. 그래서 싱글톤을 구현하는 방법이 여러개다. 상황에 맞게 골라서 사용하면 된다.

 

방법1. private 생성자와 public 게터

public class Singleton {
    private static Singleton self = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if(self == null) {
            self = new Singleton();
        }
        return self;
    }

    public void say() {
        System.out.println("I am the only one..");
    }
}
  • 장점 : 멀티스레드 환경에서 인스턴스가 중복 생성되는 문제를 막을 수 있다,
  • 단점 : 동기화를 사용하면 인스턴스를 생성하는 동안은 다른 명령이 실행되지 않는다. 만약 getInstance()가 애플리케이션에서 다른 명령들과 함께 자주 실행된다면 성능 저하의 문제가 있다.

 

방법2. 인스턴스가 필요할 때 생성하지 말고 처음부터 만들기

public class Singleton2 {
    private static Singleton2 uniqueInstance = new Singleton2();

    private Singleton2() {}

    public static Singleton2 getInstance() {
        return uniqueInstance;
    }

    public void say() {
        System.out.println("I am the only one..");
    }
}
  • 장점 : 정적 초기화 부분에서 인스턴스를 생성하므로 실행 중에 관리할 필요가 없다.
  • 단점 : 지연 생성을 사용할 수 없다. 싱글톤 인스턴스 생성 비용이 큰 경우, 프로그램 시작까지 시간이 오래 걸린다.

 

방법3. DCL

public class Singleton3 {
    private volatile static Singleton3 uniqueInstance;

    private Singleton3() {}

    public static Singleton3 getInstance() {
        if(uniqueInstance == null) {
            synchronized (Singleton3.class) {
                if(uniqueInstance == null) {
                    uniqueInstance = new Singleton3();
                }
            }
        }
        return uniqueInstance;
    }

}
  • 장점 : 지연 생성이 가능하고 속도를 크게 줄일 수 있다.
  • 단점 : voilatile은 자바 5보다 낮은 버전의 JVM에서는 동작하지 않는다.

 

방법4. enum 사용하기

멀티 스레드에서 발생하는 중복 생성 문제는 위의 방법들로 해결할 수 있다.

하지만 역직렬화 과정에서 발생하는 중복 생성은 모든 필드를 transient로 선언하고, readResolve 메소드를 추가해야한다. 게다가 리플렉션으로 private 생성자를 조작해서 사용하면 싱글톤이 깨진다.

public enum SingletonEnum {
    UNIQUE_INSTANCE;

    public void say() {
        System.out.println("I am the only one..");
    }
}

public class SingletonClient {
    public static void main(String[] args) {
        SingletonEnum singleton1 = SingletonEnum.UNIQUE_INSTANCE;
        singleton1.say();

        SingletonEnum singleton2 = SingletonEnum.UNIQUE_INSTANCE;
        singleton2.say();

        System.out.println(singleton1 == singleton2);
    }
}

 

왜 enum인가?

enum은 클래스처럼 메소드, 생성자를 모두 가질 수 있으며 private 생성자를 가진다. enum은 고정된 상수들의 집합이므로 런타임이 아닌 컴파일 타임에 모든 값을 알고 있어야 한다. 컴파일 타임에 인스턴스가 생성되고 나면 enum 클래스 내에서도 인스턴스 생성이 불가능하다. 따라서 직렬화와 역직렬화의 중복 생성을 방지할 수 있다. 리플렉션의 경우 생성자 코드를 가져오는 단계에서 enum의 생성자 코드를 얻을 수 없으므로 중복 생성을 방지할 수 있다.

enum을 사용한 싱글톤은 동기화, 클래스 로딩, 리플렉션, 직렬화와 역직렬화 문제를 해결할 수 있다. 자바에서 싱글톤을 생성하는 가장 완벽한 방법이다.

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

Adapter  (0) 2023.02.24
Command  (0) 2023.02.24
Simple Factory, Factory Method, Abstract Factory  (0) 2023.02.24
Decorator  (0) 2023.02.24
Observer  (0) 2023.02.24