본문 바로가기

IT Book Summary/디자인패턴

Singleton Pattern 싱글턴 패턴

인스턴스가 하나뿐인 특별한 객체를 만들 수 있게 해 주는 패턴.

특정 클래스에 대해서 객체 인스턴스가 하나만 만들어질 수 있도록 한다.

 

객체 중에 하나만 있어야 하는것이 있다.  예를들면 스레드 풀, 캐시, 대화상자, 레지스트리 설정을 처리하는 객체, 로그 기록용 객체,

디바이스를 위한 디바이스 드라이버 등. 인스턴스가 두개 이상 만들어지면 일관성이 없어지거나 자원을 불필요하게 잡아먹을수 있다.

 

어떻게 하면 한 클래스의 인스턴스가 두개 이상 만들어지지 않도록 할 수 있을까?

 


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

 

public class Singleton {
	// Singletno 클래스의 인스턴스를 저장하기 위한 정적 변수.
	private static Singleton uniqueInstance;
    
    // 생성자를 private 으로 선언해서 Singleton에서만 클래스의 인스턴스를 만들수 있음.
    private Singleton() {}
    
    public static Singleton getInstance() {
    	// 클래스의 인스턴스가 없을경우 만들어서 리턴, 있을경우 기존의 인스턴스 리턴.
    	if(uniqueInstance == null) {
        	uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

 


초콜릿 공장 예제

 

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 drain() {
    	if(!isEmpty() && isBoiled()) {
        	empty = true;
        }
    }
    
    public void boil() {
    	if(!isEmpty() && !isBoiled()){
        	boiled = true;
        }
    }
    
    public boolean isEmpty() {
    	return empty();
    }
    
    public boolean isBoiled() {
    	return boiled;
    }

}

 

ChocolateBoiler 인스턴스가 두개 이상 만들어 지지 않게 싱글턴으로 업그레이드 하자

 

public class ChocolateBoiler {
	private boolean empty;
    private boolean boiled;
    private static ChocolateBoiler uniqueChocolateBoiler;
    
    private ChocolateBoiler () {
    	empty = true;
        boiled = false;
        // 보일러가 비어있을때만 돌아감.
    }
    
    public static getInstance() {
    	if(uniqueChocolateBoiler == null) {
        	uniqueChocolateBoiler = new ChocolateBoiler();
        }
        return uniqueChocolateBoiler;
    }
    
    public void fill() {
    	if(isEmpty()) {
        	empty = false;
            boiled = false;
            // 보일러에 우유/초콜릿 혼합재료를 넣음.
        }
    }
    
    public void drain() {
    	if(!isEmpty() && isBoiled()) {
        	empty = true;
        }
    }
    
    public void boil() {
    	if(!isEmpty() && !isBoiled()){
        	boiled = true;
        }
    }
    
    public boolean isEmpty() {
    	return empty();
    }
    
    public boolean isBoiled() {
    	return boiled;
    }

}

 


싱글턴 패턴이란?

-> 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든 그 인스턴스에 접근할 수 있도록 하기위한 패턴.

 

  • 클래스에서 자신 하나뿐인 인스턴스를 관리하도록 한다. 
    인스턴스가 필요하면 반드시 클래스 자신을 거치도록 한다.
  • 어디서든 그 인스턴스에 접근할 수 있도록 만들어야 한다.

두 개의 스레드가 생성되면 다른 보일러 객체를 사용하게되서 문제가 될 수도 있을까?


멀티스레딩 문제 해결 방법

 

동기화 시키면 멀티스레딩 문제가 해결됨.

 

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

 

대부분의 자바 애플리케이션에서 싱글턴이 다중 스레드 환경에서 돌아갈 수 있도록 만들어야 하므로 

다른 방법도 생각해보다.

 

1. 속도가 중요하지 않으면 그냥 두자.

 

2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들자.

// 클래스가 로딩될 때 JVM에서 Singleton의 유일한 인스턴스를 생성해 준다.
// JVM에서 유일한 인스턴슬르 생성하기 전에는 어떤 스레드도 정적 변수에 접근할 수 없음.
public class Singleton {
	// 정적 초기화 부분에서 인스턴스를 생성.
	private static Singleton uniqueInstance = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    	// 인스턴스가 이미 있으니 그냥 리턴.
    	return uniqueInstance;
    }
}

 

3. DCL (Double checking locking) 을 써서 getInstance() 에서 동기화되느 부분을 줄이자.

 

인스턴스가 생성되어 있는지 확인한 후, 생성되어 있지 않았을때만 동기화.

처음에만 동기화하고 나중에는 동기화하지 않는다. 오버헤드를 줄일 수 있다.

 

public class Singleton {
	private volatile static Singleton uniqueInstance;
    
    // 생성자를 private 으로 선언해서 Singleton에서만 클래스의 인스턴스를 만들수 있음.
    private Singleton() {}
    
    public static Singleton getInstance() {
    	// 인스턴스가 있느지 확인하고 없으면 동기화 블럭으로 들어감. 처음에만 동기화
    	if(uniqueInstance == null) {
        	synchronized (Singleton.class) {
            	// 블럭에서 다시한번 변수가 null인지 확인하고 인스턴스 생성해 줌.
            	if(uniqueInstance == null) {
                	uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

 


 

  • 싱글턴 패턴은 올바르게 구현하는게 어렵지만, 인스턴스 개수를 제한해야 하는 상활에서는 언제든지 사용가능하다.
  • 싱글턴은 자신의 인스턴스를 관리하는 것 외에도 원래 그 인스턴스를 사용하고자 하는 목적에 부합하는 작업을 책임져야 한다.
  • 싱글턴은 제한된 용도로 특수한 상황에서 사용하기 위해 만들어진 것이다.
  • 클래스로더가 여러개 있으면 싱글턴이 제대로 작동하지 않고, 여러개 인스턴스가 생길 수 있음.

 

자바의 전역변수는 기본적으로 객체에 대한 정적 레퍼런스이다. 전역변수가 싱글턴보다 나쁜이유는?

-> 게으른 인스턴스 생성을 할 수 없고, 처음부터 끝까지 인스턴스를 가지고 있어야 함.

    전역변수를 사용하다보면 간단한 객체에 대한 전역 레퍼런스를 만들게 되어 네임스페이스를 지저분하게 만드는 경향.