본문 바로가기
Programming

디자인패턴 시리즈 2. 싱글톤 패턴 (Singleton Pattern)

by LeeJ1Hyun 2023. 1. 12.

싱글톤 패턴 (Singleton Pattern)

클래스 인스턴스를 단 하나만 생성하고 그 인스턴스로의 전역 접근을 제공한다.

 

코드에 적용해보기

싱글톤 패턴의 첫인상은 클래스가 단 하나뿐인데 디자인패턴이라는 거창한 이름을 붙인 것만 같았다. 하지만 생각보다 많은 곳에서 필요로 하는 디자인패턴이다. 스레드 풀, 블루투스 장치를 위한 드라이버 등이 대표적인 예시이다. 스프링 Bean은 컨테이너가 싱글톤 스콥으로 관리하고 있다고 한다. 이런 객체를 쓸 때 인스턴스가 2개 이상이면 이상하게 작동하거나 불필요하게 자원을 낭비하게 된다. 가장 심각한 문제는 결과의 일관성이 사라진다는 것이다.

 

 

싱글턴 패턴에서 인스턴스를 하나만 생성하는 방법은 간단하다. public으로 생성자를 만드는 것이 일반적이지만 안타깝게도 private으로만 생성자를 만들 수 있다. private으로 된 생성자를 통해 인스턴스를 만드는 법은 바로 getInstance()라는 메소드를 통한다. 이 메소드를 호출하면 인스턴스가 생성되어 있지 않다면 생성해서 념겨주고, 이미 생성되어 있다면 바로 넘겨준다.

 

자원을 많이 잡아먹는 인스턴스가 있다면 딱 필요할 때만 인스턴스를 생성해서 사용할 수 있도록 제한하면 된다. 이것이 바로 싱글턴 패턴의 장점 중 하나이다.

 

public class Singleton {

    private static Singleton instance;
    
    private Singleton() { }

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

 

instance라는 정적(클래스) 변수에 싱글턴의 인스턴스가 저장될 것이다. 어디서든 Singleton.getInstance()을 호출하면 인스턴스를 얻을 수 있다.

 

만약 이 코드가 멀티스레드로 돌아가면 문제가 생긴다. getInstance() 메소드에서 작업을 처리하는 과정을 보면 겹칠 수 있는 여지가 있다.

 

싱글톤 패턴이 멀티스레드 환경에서 동작했을 때

 

스레드1, 스레드2에서 각각 getInstance()를 호출했을 때 다음과 같이 인스턴스 2개가 만들어진 것을 볼 수 있다. 하나의 인스턴스만 만들도록 설계된 환경에 이런 문제가 생긴다면 자칫 큰일이 날 수 있다. 다행히도 멀티스레드 환경에서도 인스턴스가 1개이상 만들어지지 않도록 제한할 수 있는 방법이 있다.

 

1. synchronized 키워드 붙이기

 

public class Singleton {

    private static Singleton instance;
    
    private Singleton() { }

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

 

하지만 한 스레드가 메소드 사용을 끝낼 때까지 다른 스레드는 대기를 해야 한다. 속도의 문제가 발생한다. 사실 생각해보면 동기화가 필요한 시점은 getInstance() 메소드가 시작될 때만이다. instance 변수에 Singleton 인스턴스를 대입한 순간 동기화를 유지할 필요가 없기 때문이다. 속도가 중요하지 않을 때 사용해도 된다.

 

2. 처음부터 인스턴스 만들기

 

public class Singleton {

    private static Singleton instance = new Singleton();
    
    private Singleton() { }

    public static Singleton getInstance() {
        return instance;
    }
}

 

클래스 변수에 처음부터 Singleton의 인스턴스를 생성하면 멀티스레드 환경에서도 문제가 없다.

 

3. Double-Checked Locking 사용하기

 

public class Singleton {

    private volatile static Singleton instance;
    
    private Singleton() { }

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

 

volatile 키워드를 사용하면 Main Memory 영역을 참조하게 되는데 다른 스레드라도 같은 메모리 주소를 참조하게 된다. 멀티스레드 환경에서도 instance 변수가 Singleton 인스턴스로 초기화 되는 과정이 정상적으로 작동할 수 있게 해준다. 변수의 값을 읽어올 때 CPU cache가 아닌 Main Memory에서 읽어오기 때문이다.

 

인스턴스의 유무를 확인하고 없다면 동기화 스코프 내부로 들어가고 다시 한번 유무를 확인한 후 인스턴스를 생성한다. 이러한 방식으로 구현하면 인스턴스가 없을 때만 동기화할 수 있다. getInstance() 사용 속도를 대폭 줄일 수 있게 되었다.

 

4. enum 사용하기

 

가장 강력한 방법이다. 인스턴스가 JVM 내에 하나만 존재한다는 것이 보장 되므로 동기화 문제 뿐만 아니라, 클래스 로딩 문제, 리플렉션 등도 해결 가능하기 때문이다. 

 

public enum Singleton {
    INSTANCE;
}

 

public class SingletonEnumClient {
	public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        // singleton 사용 ...
    }
}

 

  • 장점
    1. 인스턴스 한번만을 생성하므로 고정된 메모리 영역을 사용하기 때문에 자원 낭비를 예방한다.
    2. 전역으로 사용할 수 있는 인스턴스이므로 다른 클래스에서 접근이 용이하다.

  • 단점
    1. 테스트 하기 어렵다. 인스턴스 자원을 공유하여 사용하기 때문에 각각의 메소드들을 테스트 하려면 매번 인스턴스를 새로 생성해야 한다.
    2. OCP, DIP를 위반한다는 의견도 있으며 종종 사실이다. 객체 지향적인 면에선 옳지 못하다.

 

 

 

 

 

* 아래의 자료들을 참고하였습니다.

에릭 프리먼 · 엘리자베스 롭슨 · 케이시 시에라 · 버트 베이츠, 『헤드퍼스트 디자인패턴 개정판』, 서환수 옮김, 한빛미디어(2022), p205-222.

 

싱글톤(Singleton) 패턴이란?

이번 글에서는 디자인 패턴의 종류 중 하나인 싱글톤 패턴에 대해 알아보자. 싱글톤 패턴이 무엇인지, 패턴 구현 시 주의할 점은 무엇인지에 대해 알아보는 것만으로도 많은 도움이 될 것이라

tecoble.techcourse.co.kr

 

댓글