CS - Singleton Pattern


1. Singleton Pattern ??

  • 싱글턴 패턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근​(액세스) 지점을 제공하는 생성 디자인 패턴이다.
  • 우리가 싱글턴 패턴을 사용하는 이유는 메모리 측면에서 좋기 때문이며, 데이터 공유가 쉽다는 이유가 있다. 왜냐하면 static을 사용하여 정적으로 하나의 인스턴스만 갖기 때문이다.
  • 이 외에도 도메인 관점에서 인스턴스가 한 개만 존재하는 것을 보증하고 싶은 경우에 싱글턴 패턴을 사용한다.



2. 스프링 환경에서의 싱글턴

  • 우리가 Spring 환경에서 Service나 Repository 등등을 만드는데, 여기서 어노테이션을 사용하면 자동으로 싱글턴 컨테이너에서 관리를 해준다. 그리고 멀티스레드 환경도 지원해 준다.
  • 그래서 싱글턴 컨테이너에 등록된 클래스 안에 변수를 선언해서는 안된다. ( final 처럼 상수는 선언해도 된다. )
      @Repository
      public class UserDao {
    		
          private User user;
    		
          public User get(String id){
              ...
              this.user = new User();
              ...
              return this.user;
          }
    		
      }
    
  • 해당 코드를 정상적으로 사용하려면 아래 코드처럼 수정 해야한다.
      @Repository
      public class UserDao {
          public User get(String id){
              User user = new User();
              return user;
          }
      }
    



3. 최근 싱글턴 패턴의 추세

  • 우리가 흔히 아는 싱글턴 방식은 아래 코드와 같다. ( Lazy Initialization )
      public class MySingletonBean {
    
          private static MySingletonBean instance;
    
          private MySingletonBean() {
              // private 생성자로 외부에서 직접 객체 생성을 방지
          }
    
          public static synchronzied MySingletonBean getInstance() {
              if (instance == null) {
                  instance = new MySingletonBean();
              }
              return instance;
          }
    
          public void doSomething() {
              System.out.println("Singleton Bean is doing something.");
          }
      }
    
  • 그러나 해당 코드는 멀티 스레드 환경에서의 문제도 있고, synchronized 처리를 하면 많은 스레드가 한 번에 접근할 경우 오랜시간 Block이 되므로 요즘은 권장하지 않는다.


  • 최근에는 크게 2가지 방식을 사용한다. ** Bill Pugh Solution**
      class Singleton {
    
          private Singleton() {}
    
          // static 내부 클래스를 이용
          // Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
          private static class SingleInstanceHolder {
              private static final Singleton INSTANCE = new Singleton();
          }
    
          public static Singleton getInstance() {
              return SingleInstanceHolder.INSTANCE;
          }
      }
    
  • 이 방법은 멀티 스레드 환경에서도 안전하다.
  • 그리고 클래스 안에 내부 클래스를 두어 JVM 클래스 로더 매커니즘과 클래스가 로드 되는 시점을 이용한 방법으로 Lazy Loading도 가능하다. ( 나중에 객체 생성을 할 수 있다는 이야기이다. )
  • 또한 static 메서드안에서는 static 맴버만 호출할 수 있기 때문에 내부 클래서를 static으로 설정하여 메모리 누수 문제도 해결할 수 있다.
  • 단, 클라이언트가 직렬화/역직렬화를 통해 임의로 싱글턴을 파괴할 수 있는 문제가 있다.


Enum을 이용하는 방법

  • enum은 애초에 한 번만 초기화 되기 때문에 스레드 세이프가 되고, 상수 뿐 아니라 변수도 선언 가능하기 때문에 이를 이용하여 싱글턴 클래스처럼 사용할 수 있다. 그리고 클라이언트의 임의 변경 또한 불가능하다.
  • 다만 enum이므로 상속이 안되고, 개발 과정에서 반드시 클래스로 써야하는 경우가 생긴다면( 설계 변경 등 ), 처음부터 다시 코드를 만들어야 하는 단점이 있다.
      enum SingletonEnum {
          INSTANCE;
    
          private final Client dbClient;
    		
          SingletonEnum() {
              dbClient = Database.getClient();
          }
    
          public static SingletonEnum getInstance() {
              return INSTANCE;
          }
    
          public Client getClient() {
              return dbClient;
          }
      }
    
      public class Main {
          public static void main(String[] args) {
              SingletonEnum singleton = SingletonEnum.getInstance();
              singleton.getClient();
          }
      }
    


  • 성능이 중요시 될 때는 Bill Pugh Solution
  • 직렬화, 안정성이 중요시 될 때는 enum
  • 이렇게 사용하는게 요즘 싱글턴 패턴의 추세이다.



4. 싱글턴 패턴의 장,단점

  • 장점
    • 클래스가 하나의 인스턴스만 갖는다는 확신을 명시적으로 줄 수 있음.
    • 메모리 측면에서 효율적임
    • 데이터 공유가 좋음.


  • 단점
    • 멀티 스레드 환경에서는 위처럼 별도의 처리 과정이 필요하다.
    • 클라이언트 코드를 유닛 테스트하기 어렵다. ( 매번 인스턴스 상태를 초기화 시켜줘야 하기 때문이다. 안그러면 애플리케이션 전체에서 상태를 공유하기 때문이다.)
    • 한 번에 두 가지 문제를 동시에 해결하므로 SRP를 위반하고, new 키워드를 직접 사용하여 클래스 안에서 객체를 생성하고 있으므로 DIP 원칙을 위반하며, 그 결과 OCP 원칙을 위반하게 될 가능성이 크다.
    • 자식클래스를 만들 수 없고, 내부 상태를 변경하기 어려우므로 유연성이 낮다.
    • 코드 구현 자체에 시간이 필요하다.



5. 결론

  • 싱글턴 패턴은 Trade-off가 명확하기 때문에, 꼭 필요한 경우에만 사용하며 너무 무분별하게 사용하지 않도록 해야한다. 또한 Spring 환경에서는 어노테이션을 통해 싱글턴 컨테이너에 등록이되고 자동 관리가 되기때문에 편리하지만, 멀티스레드 환경임을 고려하여 해당 클래스에 변경 가능한 필드를 직접 선언해서는 안된다. ( final 같이 상수는 가능하다. )






results matching ""

    No results matching ""