본문 바로가기
야미스터디/Design pattern

[디자인 패턴] 싱글톤 패턴 📌

by 의정부핵꿀밤 2022. 8. 21.
728x90

객체

  • 객체 : 속성기능을 갖춘 것
  • 클래스 : 속성과 기능을 정의한 것
  • 인스턴스 : 속성과 기능을 가진 것 중 실제 하는 것

 


싱글톤 패턴 (Singleton Pattern)

  • 객체의 인스턴스가 오직 1개만 생성되는 패턴
  • 애플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(static), 그 메모리에 인스턴스를 만들어 사용한다
  • 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고, 최초 생성 이후에 호출된 생성자는 최초에 생성한 객체를 반환한다
  • 즉, 인스턴스가 필요할 때 똑같은 인스턴스를 만들지 않고, 기존 인스턴스를 사용하게 하는 디자인 패턴이다
  • 단, 싱글톤 인스턴스를 생성할 때에는 동시성(Concurrency) 문제를 고려해서 설계해야 한다

 

 

 

싱글톤 패턴 구현

다음은 싱글톤 패턴을 통해 객체를 미리 생성해두고 가져오는 가장 단순하고 안전한 방법이다

public class Singleton {
    // 단 1개만 존재해야 하는 객체의 인스턴스, static 으로 선언
    private static Singleton instance;

    // private 생성자로 외부에서 객체 생성을 막음
    private Singleton() {}

    // 외부에서는 getInstance() 로 instance 를 반환
    public static Singleton getInstance() {
      // instance 가 null 일 때만 생성
      if (instance == null){
          instance = new Singleton();
      }
      return instance; 
    } 
}
  • 자바에서는 위와 같이 생성자를 private으로 선언하여 생성 불가하게 하고, getInstance()로 받아서 사용한다
  • 따라서 getInstance() 가 여러번 호출되더라도 단 하나의 동일한 instance만을 반환한다
  • 그러나 위의 코드는 멀티 스레드 환경에서 Thread-Safe를 보장해주지 않는다

 

 

 

 

🎃 Multi-Thread에서 발생할 수 있는 싱글톤의 문제점

1. 여러 개의 인스턴스 생성

public static Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}
  • Multi-thread 환경에서 Instance가 없을 때 동시에 위의 getInstance() 메서드를 실행하는 경우, 각각 새로운 Instance를 생성할 수 있다

 

2. 변수 값의 일관성 실패

public class Singleton {
    private static Singleton instance;
    private static int count = 0;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    public static void plusCount() {
        count++;
    }
}
  • 위와 같은 코드가 실행되었을 때 여러 개의 thread에서 plustCount() 메소드를 동시에 실행한다면 일관되지 않은 값들이 생길 수 있다

 

 

 

🎰 Multi-Thread에서의 싱글톤 구현

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 동기화를 활용해 스레드를 안전하게 만든다
그러나, 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번의 성능 저하를 완화시키는 방법이다
  • 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번이다!

 

 

 

 

⭕ 싱글톤 패턴을 사용하는 이유

1. 메모리 측면에서의 이점

  • 최초 한 번의 new 연산자를 통해서 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다
  • 또한 이미 생성된 인스턴스를 활용하기 때문에 객체 로딩 시간이 현저히 줄어들어 속도 측면에서도 이점이 있다

 

2. 다른 클래스 간의 쉬운 데이터 공유

  • 싱글톤으로 만들어진 인스턴스는 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다
  • DBCP(Database Connection Pool)처럼 공통된 객체를 여러개 생성해서 사용해야 하는 상황에서 많이 사용한다
    • Thread Pool, Cache, Dialog box(대화상자), 사용자 설정, 레지스트리 설정, 로그 기록 객체 등..
  • 그러나 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시에 접근하게 되면 동시성 문제가 발생할 수 있기 때문에 이 점을 유의하여 설계해야 한다

 

3. 도메인 관점

  • 인스턴스가 한 개만 존재하는 것을 보증하고 싶은 경우 싱글톤 패턴을 사용하기도 한다

 

 

 

 

❌ 싱글톤 패턴의 문제점

1. 싱글톤 패턴을 구현하는 코드 자체가 많이 필요하다

  • 위에서 본 구현 방법 외에도 정적 팩토리 메서드에서 객체 생성을 확인하고 생성자를 호출하는 경우에 멀티스레딩 환경에서 발생할 수 있는 동시성 문제 해결을 위해 Syncronized 키워드를 사용해야 한다

 

2. 테스트하기 어렵다

  • 싱글톤 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결정적으로 격리된 환경에서 수행되려면 매번 인스턴스의 상태를 초기화 시켜줘야 한다
  • 그렇지 않으면 애플리케이션 전역에서 상태를 공유하기 때문에 테스트가 온전하게 수행되지 못한다

 

3. 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다

  • new 키워드를 직접 사용하여 클래스 안에서 객체를 생성하고 있으므로, 이는 SOLID 원칙 중 DIP를 위반하게 되고, OCP 원칙 또한 위반할 가능성이 높다
    • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우, 다른 클래스의 인스턴스들 간에 결합도가 높아져 OCP 원칙을 위배하게 된다
    • 따라서 수정이 어려워지고 테스트 또한 어려워진다

 

4. 이외의 문제점

  • 자식클래스를 만들 수 없다
  • 내부 상태를 변경하기 어렵다
  • 멀티쓰레드 환경에서 동기화 처리를 안하면 인스턴스 두 개가 생성될 수 있다

 

 

결과적으로 싱글톤 패턴은 유연성이 많이 떨어지는 패턴이라고 할 수 있다

 

 

 

 

💡 결론

  • 싱글톤 패턴을 사용하게 되면 오직 한 개의 인스턴스 생성을 보증하여 효율을 찾을 수 있지만, 그에 못지않게 수반되는 문제점도 많다
  • 싱글톤 패턴은 '안티패턴'이라고 불릴 만큼 단독으로 사용하면 객체 지향에 위반되는 사례가 많다
  • 그러나 스프링 컨테이너 같은 프레임워크의 도움을 받으면 싱글톤 패턴의 문제점들을 보완하면서 장점의 혜택을 누릴 수 있다
  • 실제로 스프링 빈은 컨테이너의 도움을 받아 싱글톤 스콥으로 관리되고 있다
  • 따라서 프레임워크의 도움없이 싱글톤 패턴을 적용하고 싶다면, 싱글톤의 장단점의 trade-off를 잘 고려하여 사용하는 것이 좋다!

 

 

 


참고)

https://www.youtube.com/watch?v=5jgpu9-ywtY 

https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/

 

싱글톤(Singleton) 패턴이란?

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

tecoble.techcourse.co.kr

https://jeong-pro.tistory.com/86

 

싱글톤 패턴(Singleton pattern)을 쓰는 이유와 문제점

싱글톤 패턴(Singleton Pattern) 싱글톤 패턴 애플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(Static) 그 메모리에 인스턴스를 만들어 사용하는 디자인패턴. 생성자가 여러

jeong-pro.tistory.com

https://injae-kim.github.io/dev/2020/08/06/singleton-pattern-usage.html

 

Injae's devlog

현실의 문제를 해결하는 엔지니어

injae-kim.github.io

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

 

싱글톤 패턴(Singleton pattern) | 👨🏻‍💻 Tech Interview

싱글톤 패턴(Singleton pattern) 애플리케이션이 시작될 때, 어떤 클래스가 최초 한 번만 메모리를 할당(static)하고 해당 메모리에 인스턴스를 만들어 사용하는 패턴 즉, 싱글톤 패턴은 '하나'의 인스

gyoogle.dev

https://velog.io/@seongwon97/%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80

 

싱글톤(Singleton) 패턴이란?

싱글톤 패턴은 객체의 인스턴스를 한개만 생성되게 하는 패턴입니다.

velog.io

728x90

댓글