[CS/디자인패턴] 싱글톤 패턴이란? 사용이유, 구현시 주의점

나른한 개발자·2023년 7월 6일
0

CS

목록 보기
8/11

싱글톤 패턴 (Singletone Pattern)

애플리케이션이 시작될 때, 어떤 클래스가 최초 한번만 메모리를 할당하고 해당 메모리에 인스턴스를 만들어 사용하는 패턴.

싱글톤 패턴은 클래스의 인스턴스를 하나만 만들어 사용하는 패턴을 말한다. 주로 데이터 커넥션 풀이나 스레드 풀, 캐시, 로그 기록 객체와 같이 공통된 객체를 여러개 생성해야하는 상황에 사용한다.

장점

  • 클래스의 인스턴스가 요청이 올 때마다 생성되는 것이 아닌, 단 한 개만 생성되기 때문에 메모리 낭비를 방지할 수 있다.
  • 인스턴스가 전역이기 때문에 클래스의 인스턴스들이 데이터를 공유하기 쉽다.
  • 이미 생성된 인스턴스를 사용하기 때문에 속도 측면에서 이점이 있다.

단점

  • 하나의 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유할 경우 클래스 간 결합도가 높아져, OCP 원칙을 위배할 수 있다.
  • 유지보수, 테스트가 어려워진다.
  • 멀티 스레드 환경에서 동기화처리를 해주지 않으면 인스턴스가 2개 생길 수 있음.

구현방법

private 생성자와 getInstance()를 활용

public class SingleTone{
	private static SingleTone instance;
    
    private SingleTone(){}
    
    public SingleTone getInstance(){
    	if (instance == null){
        	return new SingleTone();
        }
        return instance;
    }
}

자바에서는 private static으로 인스턴스를 선언하고, getInstance()를 정의하여 클래스의 객체가 생성되어 있다면 생성된 객체를 반환하고 그렇지 않다면 새로 생성하는 방식으로 구현할 수 있다.

이때, 외부에서 new를 사용하여 객체를 생성할 수 없도록 생성자를 private으로 만들어야 한다.

멀티스레드에서의 싱글톤

멀티 스레드 환경에서는 위와 같은 방식으로 구현한다면 다음과 같은 문제가 생길 수 있다.

여러개의 인스턴스 생성

바로 위 예시 코드에서 보면 인스턴스를 반환하기전에 인스턴스가 생성되어 있는지의 여부를 확인하는 부분이 있다. 만약 멀티스레스 환경에서 instance가 아직 생성되지 않았을 때 getInstance()를 실행하게 된다면 인스턴스가 없다고 판단되어 여러개의 인스턴스가 생기는 문제가 발생한다.

해결 (1) - synchronized 동기화

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

위와 같이 synchronized 동기화를 사용하여 스레드를 안전하게 만들어 해결할 수 있다. 하지만 synchronized는 성능 저하를 일으킬 수 있어 권장 되지 않는다.

해결 (2) - Double-checked Locking

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

instance가 null인 경우에만 동기화를 하여 성능을 개선시켰다. 처음 인스턴스 생성 이후에는 동기화를 하지 않기 때문에 성능 저하를 완화시킬수 있다.

하지만 개발자가 직접 동기화를 제어하는 경우에는 프로그램 구조가 복잡해지거나 비용문제가 발생할 수 있다. 따라서 다음 3번의 방식이 일반적으로 사용된다.

해결 (3) - JVM 클래스 초기화 시점 활용

public class SingleTone{
	private static SingTone instance = new SingleTone();
    
    private SingleTone(){};
    
    public static SingleTone getInstance(){
    	return instance;
    }
}

클래스가 로드될 때 초기에 인스턴스를 생성한다면 멀티 스레드 환경에서도 하나의 인스턴스를 공유할 수 있다.


싱글톤 패턴 구현 시 주의점

싱글톤 패턴 구현 시에 주의해야할 점은 싱글톤 객체는 반드시 무상태(stateless)여야 한다는 것이다. 싱글톤 객체가 stateful 할 경우 데이터 일관성이 깨질 수 있다.

public class Order{
	private static Order instance = new Order();
	private int price;
    
    public void order(int price){
   		this.price= price;
   	}
    
    public int getPrice(){
    	return price;
    }
    
    private Order(){}
    public static Order getInstance(){return instance;}
    
}
public class Main{
	public static void main(String args[]){
    	Order thread1 = Order.getInstance();
        Order thread2 = Order.getInstance();
        
        thread1.order(1000);
		thread2.order(2000);
        
        System.out.println(thread1.getPrice()); /// 2000
    }
}

멀티 스레드 환경이라고 가정해서봤을 때, 여러 스레드에서 동시에 price 필드에 접근하므로써 데이터의 일관성이 깨지게 되었다. thread1의 유저는 1000원을 결제하였지만 주문 가격을 조회하니 2000원으로 나오는 것이다. (실제 서비스라고 생각하면 아찔한 상황이다.)

따라서 싱글톤 패턴을 사용할 때에는 무상태로 설계하여 이러한 문제를 예방해야한다.

  • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
  • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야한다.
  • 필드 대신 공유되지 않는 지역변수, 파라미터, ThreadLocal을 사용해야한다.

위 코드는 다음과 같이 수정할 수 있다.

public class Order{
	private static Order instance = new Order();
	private int price;
    
    public void order(int price){
        // .. 내부로직..
   		return price;
   	}
    
    private Order(){}
    public static Order getInstance(){return instance;}
    
}
public class Main{
	public static void main(String args[]){
    	Order thread1 = Order.getInstance();
        Order thread2 = Order.getInstance();
        
        int price1 = thread1.order(1000);
		int price2 = thread2.order(2000);
        
        System.out.println(price1); /// 1000
    }
}


참고
싱글톤 패턴(Singleton pattern)
싱글톤 패턴이란?

profile
Start fast to fail fast

0개의 댓글