CS(Computer Science)/Design Pattern

싱글톤 패턴

환성 2023. 1. 8. 23:17
728x90
애플리케이션이 시작될 때, 어떤 클래스가 최초 한 번만 메모리를 할당하고 해당 메모리에 인스턴스르 만들어 사용하는 패턴
하나의 인스턴스만 생성하여 사용하는 디자인 패턴

싱글톤 패턴 설계 예시

 

  • 생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것이다
  • (java에서는 생성자를 private으로 선언해 다른 곳에서 생성하지 못하도록 만들고, getInstance() 메소드를 통해 받아서 사용하도록 구현한다)

 

사용 이유

  • 한번에 new 연산자를 통해 고정된 메모리 영역을 사용하기에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다.
  • 메모리 측면에서 이득을 볼 수 있기 때문이다.
  • 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능한 장점이 있다.

 

단점

  • 코드 자체가 많이 필요하다
  • 테스트하기가 어렵다(싱글톤 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결정적으로 격리된 환경에서 수행되려면 매번 인스턴스의 상태를 초기화시켜주어야 한다.)
  • 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다(new 키워드를 직접 사용하여 클래스 안에서 객체를 생성하고 있으므로, SOLID 원칙 중 DIP를 위반하고 OCP원칙 또한 위반)
  • 자식 클래스를 만들수 없고 내부 상태를 변경하기가 어렵다.

 

 

멀티스레드 환경에서 안전한 싱글톤 만드는 방법

 

1. Lazy Initialization(게으른 초기화)

public class ThreadSafe_Lazy_Initialization{
 
    private static ThreadSafe_Lazy_Initialization instance;
 
    private ThreadSafe_Lazy_Initialization(){}
     
    public static synchronized ThreadSafe_Lazy_Initialization getInstance(){
        if(instance == null){
            instance = new ThreadSafe_Lazy_Initialization();
        }
        return instance;
    }
 
}
  • private static으로 인스턴스 변수를 만들고, private로 생성자를 만들어 외부에서의 생성을 막는다.
  • synchronized 동기화를 활용해 스레드를 안전하게 만든다. 

 

2. Lazy Initialization + Double-checked Locking(게으른 초기화)

public class ThreadSafe_Lazy_Initialization{
    private volatile static ThreadSafe_Lazy_Initialization instance;

    private ThreadSafe_Lazy_Initialization(){}

    public static ThreadSafe_Lazy_Initialization getInstance(){
    	if(instance == null) {
        	synchronized (ThreadSafe_Lazy_Initialization.class){
                if(instance == null){
                    instance = new ThreadSafe_Lazy_Initialization();
                }
            }
        }
        return instance;
    }
}
  • 1번과는 달리, 먼저 조건문으로 인스턴스의 존재 여부를 확인한 다음 두번째 조건문에서 synchronized를 통해 동기화를 시켜 인스턴스를 생성하는 방법이다.
  • 스레드를 안전하게 만들면서, 처음 생성 이후에는 synchronized를 실행하지 않기 때문에 성능저하 완화가 가능하다.
  • 완벽한 방법은 아니다.

3. Initialization on demand holder idiom(holder에 의한 초기화)

public class Something {
    private Something() {
    }
 
    private static class LazyHolder {
        public static final Something INSTANCE = new Something();
    }
 
    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}
  • 클래스 안에 클래스(holder)를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법이다.
  • 2번처럼 동기화를 사용하지 않는 방법을 안하는 이유는, 개발자가 직접 동기화 문제에 대한 코드를 작성하면서 회피하려고 하면 프로그램 구조가 그만큼 복잡해지고 비용 문제가 발생할 수 있음. 또한 코드 자체가 정확하지 못할 때도 많다.
  • 이 때문에, 3번과 같은 방식으로 JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용해 싱글톤의 초기화 문제에 대한 책임을 JVM에게 떠넘기는 걸 활용한다.
  • 클래스 안에 선언한 클래스인 holder에서 선언된 인스턴스는 static이기 때문에 클래스 로딩시점에서 한번만 호출된다. 또한 final을 사용해서 다시 값이 할당되지 않도록 만드는 방식을 사용한 것이다.
  • 3번이 제일 많이 사용된다.

 

 

출처:

https://gyoogle.dev/blog/design-pattern/Singleton%20Pattern.html

'CS(Computer Science) > Design Pattern' 카테고리의 다른 글

옵저버 패턴  (4) 2023.01.12
팩토리 메소드 패턴  (0) 2023.01.09
템플릿 메소드 패턴  (0) 2023.01.09
어댑터 패턴  (0) 2023.01.08