싱글톤(Singleton) 패턴

weekbelt·2022년 1월 16일
1

디자인패턴

목록 보기
1/4

싱글톤(Singleton) 패턴

1. 정의

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one "single" instance. This is useful when exactly one object is needed to coordinate actions across the system.

위키피디아에서 이런식으로 설명이 되어있습니다.

한글로 설명하면 싱글턴 패턴은 해당 클래스의 인스턴스가 하나 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다.

2. 사용

2.1 싱글톤은 어디서 사용할까?

객체 중에 하나만 있으면 되는 것들에 쓰입니다. 스레드 풀이라든가 캐시, 대화상자, 사용자 설정, 레지스트리 설정을 처리하는 객체, 로그 기록용 객체, 프린터나 그래픽 카드 같은 디바이스를 위한 디바이스 드라이버 같은 걸 예로 들 수 있습니다.

2.2 고전적인 싱글턴 패턴 구현

public class Singleton {

    private static Singleton uniqueInstance; // 01

    private Singleton() {
    }

    public static Singleton getInstance() {  // 02
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

01: Singleton 클래스의 유일한 인스턴스를 저장하기 위한 정적 변수입니다. 싱글턴패턴은 단일 책임 원칙(Single Reponsibility Principle)원칙을 위반하면서 두 가지 문제를 동시해 해결해 줍니다.

02: 클래스의 인스턴스를 만들어서 리턴해주는 로직입니다. 아직 인스턴스가 만들어지지 않았다면 private으로 선언된 생성자를 이용해서 객체를 만든다음 uniqueInstance에 그 객체를 대입해서 리턴합니다. 이렇게 하면 인스턴스가 필요한 상황이 닥치기 전에는 아예 인스턴스를 생성하지 않게 됩니다. 이런 방법을 "게으른 인스턴스 생성(lazy instantiation)" 이라고 부릅니다.

3. 문제

하지만 고전적인 싱글턴 패턴 구현에서 문제점이 있습니다. getInstance()메서드 자체는 atomic하지 않기 때문에 하나의 스레드에서는 문제가 없지만 만약에 두개의 스레드에서 코드를 실행시킨다고 가정해 보겠습니다

1번 스레드

public static Singleton getInstance(){

2번 스레드

public static Singleton getInstance(){

1번 스레드

if(uniqueInstance==null){

2번 스레드

if(uniqueInstance==null){

이런식으로 실행순서가 겹치게 된다면 결국 1번스레드와 2번스레드에서 각각의 인스턴스가 생겨서 싱글톤을 보장할 수 없게 됩니다.

4. 문제 해결 방법

4.1 처음부터 인스턴스를 만들어 버립니다.

public class Singleton {

    private static Singleton uniqueInstance = new Singleton(); // 01

    private Singleton() {
    }

    public static Singleton getInstance() {
        return uniqueInstance; // 02
    }
}

01: 정적 초기화 부분에서 Singleton인스턴스를 생성합니다.

02: getInstance()가 호출이되면 미리 생성된 인스턴스를 리턴만 해주면 됩니다.

4.2 getInstance()를 동기화 시킵니다.

public class Singleton {

    private static Singleton uniqueInstance; // 01

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {  // 02
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

getInstance()에 synchronized 키워드만 추가하면 한 스레드가 메소드를 사용하기 전에 다른 스레드는 기다려야하기 떄문에 싱글톤을 보장합니다. 하지만 문제점이 있는데 생성하려는 객체가 자원을 많이 소모한다면 시스템 성능에 큰 부담을 주게 됩니다.

4.3 DCL(Double-Checking Locking)을 써서 getInstance()에서 동기화되는 부분을 줄입니다.

DCL(Double-Checking Locking)을 사용하면, 일단 인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화를 할 수 있습니다.

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() { }

    public static synchronized Singleton getInstance() {   
        if (uniqueInstance == null) {                       // 01
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {               // 02
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

01: 인스턴스가 있는 확인하고, 없으면 동기화된 블럭으로 들어갑니다.

02: 블록으로 들어온 후에도 다시 한 번 변수가 null인지 확인한 다음 인스턴스를 생성합니다.

4.4 원소가 하나인 열거 타입을 선언합니다.

하지만 4.1, 4.2, 4.3방법 모두 직렬화 상황이나 리플렉션 공격으로 제2의 인스턴스가 생성되는일이 생길 수 있습니다. 열거타입으로 싱글톤을 생성하면 이러한 부분을 완벽히 막아 줍니다.

public enum Singleton {
    INSTANCE;
    
    // 메서드
}

5. Spring에서 싱글톤 사용 예

일반적으로 싱글톤은 애플리케이션에 대해 전체적으로 유니크하지만 Spring에서는 이러한 제약이 완화되어 쓰입니다.
Spring은 IoC 컨테이너당 싱글톤을 보장합니다. 그래서 Spring은 ApplicationContext당 하나의 빈만 생성합니다.

기본적으로 Spring은 모든 Bean을 싱글톤으로 만듭니다.

6. 결론

싱글턴 패턴은 안티패턴으로 알려져 있습니다.

그 이유는 많은 테스트 프레임워크가 mock 객체를 만들 때 상속에 의존하기 때문에 Singleton의 클라이언트 코드를 유닛 테스트하기가 까다롭습니다. 싱글톤 클래스의 생성자는 private이고 정적 메소드를 재정의하는 것은 대부분의 언어에서 불가능하기 때문에, 싱글톤을 스터빙할 수 있는 방법이 없다면 쓰지 않는것을 추천합니다.

안티패턴은 소프트웨어 공학 분야 용어이며, 실제 많이 사용되는 패턴이지만 비효율적이거나 비생산적인 패턴을 의미한다. 안티패턴은 1995년 앤드루 케이니그가 디자인 패턴을 참고하여 처음 사용한 말이다. 위키백과


참고

profile
백엔드 개발자 입니다

0개의 댓글