싱글턴 패턴

오형민·2021년 6월 21일
0

디자인패턴

목록 보기
1/6

싱글턴 패턴

from https://refactoring.guru/design-patterns/singleton
from https://refactoring.guru/design-patterns/singleton

  • 싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴
  • 싱글턴은 제한된 용도로 특수한 상황에서 사용해야함. 남용 x

전역변수 사용 시 단점

  • 전역변수에 객체를 대입하면 어플리케이션 시작 시 객체가 생성됨(플랫폼마다 다르지만)
  • 만약 그 객체가 자원을 많이 차지하는데 아무도 쓰지 않는다면 아무데도 쓸데없는 객체가 되어버림.
  • 따라서, 필요에 의해 생성 가능한 싱글턴 패턴을 활용!

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

생성자를 private 으로 선언하고, 클래스의 인스턴스를 생성해 리턴해주는 정적 메소드를 만듦

public class Singleton{
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    //생성자를 private으로 선언해 해당 클래스 내부에서만 인스턴스 생성 가능
    
    public static Singleton getInstance(){
    	if(uniqueInstance == null){
        	uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
	//기타 메소드..
}

//Singleton.getInstance() 이런식으로 사용하면 됨

"게으른 인스턴스 생성(lazy instantiation)"
필요한 상황이 오기 전까지 인스턴스를 생성하지 않고, 요청을 받을 때 생성하는 방법

  • 다만, 위 코드에는 문제가 있고, 뒤에서 해당 문제에 대해 확인합니다.

초코릿 공장(예시 코드)

기본 코드

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;
    
    public ChocolateBoiler() {
        empty = true;
        boiled = false;
    }
    
    public void fill() {
        if(isEmpty()){
            empty = false;
            boiled = false;
            // 보일러에 우유/초콜릿을 혼합한 재료를 집어넣음
        }
    }
    
    public void boil() {
        if(!isEmpty() && !isBoiled()){
            //재료를 끓임
            boiled = true;
        }
    }
    
    public boolean isEmpty(){
        return empty;
    }
    
    public boolean isBoiled(){
        return boiled;
    }
}

기본 싱글턴 적용 코드


public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;

    private static ChocolateBoiler uniqueInstance;
    
    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }
    
    public static ChocolateBoiler getInstance(){
        if(uniqueInstance == null){
            uniqueInstance = new ChocolateBoiler();
        }
        return uniqueInstance;
    }
    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            // 보일러에 우유/초콜릿을 혼합한 재료를 집어넣음
        }
    }

    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            //재료를 끓임
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }

}
  • 만약 애플리케이션에서 ChoculateBoiler 인스턴스가 두 개 이상 만들어지면 어떤 문제가 생길 수 있을까요?

싱글턴 패턴의 정의

싱글톤 패턴

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

  • 반드시 생성은 클래스 자신을 통해 하도록 하여, 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 합니다.
  • 싱글톤은 '게으르게' 생성할 수 있습니다. 클래스의 객체가 자원을 많이 잡아먹는 경우에 유용합니다.
1번 스레드2번 스레드uniqueInstance 값
public static Single getInstance() {null
public static Single getInstance() {
if ( uniqueInstance == null ) {
if ( uniqueInstance == null ) {
uniqueInstance = new Singleton();object 1
return uniqueInstance;
uniqueInstance = new Singleton();object 2
return uniqueInstance;

문제점 & 해결

멀티스레딩 환경에선 동기화가 필요함

public static synchronized Singleton getInstance();

-> 속도 문제 발생 / 사실 동기화가 필요한 부분은 인스턴스 변수에 객체를 대입할 때 뿐인데..

더 효율적인 방법은 없을까?

  • getInstance() 속도가 중요하지 않다면 그냥 두기 (*메소드 동기화 시 성능이 100배정도 저하된다고 함)
  • 인스턴스를 처음부터 만들어 놓기
  • DCL(Double-Checking Locking)을 써서 getInstance()에서 동기화 되는 부분을 줄이기 (자바 1.5 전은 사용x)
private volatile static Singleton uniqueInstance;
//volatile 키워드를 사용하면 멀티스레딩 환경에서 변수의 가시성을 보장해줌

public static Singleton getInstance(){
    if(uniqueInstance == null){
       	synchronized (Singleton.class){
       	    //동기화 블록 내에서도 한 번 더 null 체크
       	    if(uniqueInstance == null)
               uniqueInstance = new Singleton();
       	}
    }
    return uniqueInstance;
 }
 

Q&A

모든 메소드와 변수가 static으로 선언된 클래스를 만들어도 되지않나?

  • 복잡한 초기화가 필요없는 경우에만 가능. + 자바에서 정적 초기화를 처리하는 방법 때문에 일이 복잡해질수도
  • 특히 여러 클래스가 얽혀 있는 경우 초기화 순서와 관련된 버그가 발생할 수 있기 때문에 현실적이지 않음

클래스 로더 두 개가 각기 다른 싱글턴의 인스턴스를 가지게 될 수도 있지 않나?

맞음. 따라서 클래스 로더를 여러개 사용하면서 싱글턴을 사용한다면 주의필요. (클래스 로더 직접 지정 등)

전역변수 VS 싱글턴 패턴

전역변수는 객체에 대한 정적 레퍼런스로 '게으른 인스턴스 생성'이 불가하고, 처음부터 끝까지 가지고 있어야함
또한 간단한 객체에 대한 전역 레퍼런스를 자꾸 생성하며 네임스페이스를 지저분하게 만드는 경향이 생김

  • 자바 1.2 이전엔 싱글턴에 대한 유일한 레퍼런스가 싱글턴 자체뿐일 경우 가비지 컬렉터에 의해 제거되는 경우가 있었다고 함.

연필을 깎으며

page 221

  • getInstance() 동기화 : 속도 문제가 발생할 수 있으나 해당 예제에선 신경쓰지 않아도 될 듯

  • 인스턴스 미리 초기화 : 어차피 초콜릿 보일러는 항상 필요하기 때문에 정적 초기화도 괜찮을 듯

  • DCL 사용 : 특정 JVM 버전에선 작동하지 않기 때문에 x

0개의 댓글