티스토리 뷰

싱글톤 패턴이란?

싱글톤 패턴이란 단 하나의 유일한 객체를 만들기 위한 코드 패턴입니다.

쉽게 말하자면 메모리 절약을 위해, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 만들지 않고 기존의 인스턴스를 가져와 활용하는 기법을 말합니다. 전역 변수라는 걸 만들어 이용하는 이유는, 똑같은 데이터를 메서드마다 지역 변수로 선언해서 사용하면 무의미하기도 않고 낭비이기 때문에, 전역에서 한번만 데이터를 선언하고 가져와 사용하면 효율적이기 때문입니다.

따라서 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 그 객체가 리소스를 많이 차지하는 역할을 하는 무거운 클래스일때 적합하며,  대표적으로 데이터베이스 연결 모듈이 있습니다.

 

싱글톤 패턴 사용 방법

1. Singleton 클래스 안에 Singleton이라는 이름의 private 정적 변수를 선언합니다.

private static Singleton singletonOfClass=new Singleton();

2. Singleton 클래스의 생성자를 private으로 만듭니다.

private Signleton() { /*...Constructor code...*/}

3. 멤버를 접근하는 정적 public 메서드를 정의합니다.

public static Singleton getSingleton(){
	return singletonOfMyClass;
}

 

싱글톤 패턴의 구현 기법 종류

1. 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;
    }
}

 

2. 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;
    }
}

 

3. Lazy initialization

객체 생성에 대한 관리를 내부적으로 처리합니다. 메서드를 호출했을 때 인스턴스 변수의 null 유무에 따라 초기화 하거나 있는 걸 반환하는 기법으로, static일 경우 사용하지 않는데도 고정 메모리를 차지하는 한계점을 극복합니다. 대부분의 자료에서 (학교 수업 자료 포함) 이 방식이 싱글톤 패턴의 정석이라고 하지만 쓰레드 세이프(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;
    }
}

 

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 Loading(나중에 객체 생성) 도 가능한 완벽한 싱글톤 기법입니다. 
클래스 안에 내부 클래스(holder)를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법 (스레드 세이프함)입니다. static 메소드에서는 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;
    }
}

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

패턴 사용 시기

  • 인스턴스가 하나만 존재하는 것을 보증하고 싶을 경우
  • 메모리 낭비를 방지하고 싶을 경우
  • 공통된 객체를 여러 개 생성해야 하는 경우

패턴 장점

  • 하나의 인스터스 생성으로 메모리 낭비를 방지할 수 있습니다.
  • 전역 instance로 공유가 쉽습니다.
  • 두 번째 사용 시에는 기존에 생성된 것을 사용하기 때문에 로딩시간이 감소됩니다.

패턴 단점

  • 모듈간 의존성이 높아집니다.
  • SOLID 원칙에 위배되는 사례가 많습니다.
  • TDD 단위 테스트에 에로사항이 있습니다.

싱글톤 패턴은 유연성이 많이 떨어지는 패턴이라고 할 수 있다.

그래서 직접 유저가 만들어 사용하는 것 보다는, 스프링 컨테이너 같은 프레임워크의 도움을 받으면 싱글톤 패턴의 문제점들을 보완하면서 장점의 혜택을 누릴 수 있다. 스프링 프레임워크에서는 내부적으로 클래스의 제어를 Ioc방식의 컨테이너에게 넘겨 컨테이너가 관리하기 때문에, 이를 통해 평범한 객체도 하나의 인스턴스 뿐인 싱글톤으로 존재가 가능하기 때문에 싱글톤 단점이 없다.

 

참고

 

💠 싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자

Singleton Pattern 싱글톤 패턴은 디자인 패턴들 중에서 가장 개념적으로 간단한 패턴이다. 하지만 간단한 만큼 이 패턴에 대해 코드만 던져주고 끝내버리는 경우가 있어, 어디에 쓰이는지 어떠한 문

inpa.tistory.com

 

'CS > Design Pattern' 카테고리의 다른 글

중재자 패턴 (Mediator Pattern)  (0) 2024.04.18
옵서버 패턴 (Observer Pattern)  (0) 2024.04.18
브리지 패턴 (Bridge Pattern)  (0) 2024.04.17
컴포지트 패턴 (Composite Pattern)  (0) 2024.04.17
퍼싸드 패턴 (Facade Pattern)  (0) 2024.04.17