Singleton Pattern

2020. 4. 27. 22:40디자인 패턴

1. Singleton Pattern이란

 Singleton Pattern이란 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴입니다. 아래의 예제를 통해 Singleton Pattern을 살펴보겠습니다.

public class Printer {
  // 외부에 제공할 자기 자신의 인스턴스
  private static Printer printer = null;
  private Printer() { }
  // 자기 자신의 인스턴스를 외부에 제공
  public static Printer getPrinter(){
    if (printer == null) {
      // Printer 인스턴스 생성
      printer = new Printer();
    }
    return printer;
  }
  public void print(String str) {
    System.out.println(str);
  }
}

 위의 코드를 살펴보면, 우선 Printer 클래스의 객체를 아무 곳에서나 생성할 수 없도록 생성자를 private으로 생성했습니다. 그리고 클래스 자기 자신에 대한 객체를 static으로 하나 만들어 getPrinter라는 메소드를 통해 외부에 제공하는 메소드를 만들어줍니다. 이렇게 함으로써 클래스의 인스턴스를 통하지 않고서도 메소드를 실행할 수 있고 변수를 참조할 수 있습니다.

2. Singleton을 사용하는 이유

  • 고정된 메모리 영역을 얻으면서 하나의 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있습니다.
  • Singleton으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽습니다.
  • 두 번째 호출부터는 이미 만들어진 객체를 가져오는 것이기 때문에 객체 로딩 시간이 줄어 성능이 향상됩니다.

3. Singleton의 문제점

다중 스레드에서 객체가 만들어지지 않은 시점에 인스턴스가 1개 이상 생성될 수 있습니다.

 위의 예제를 이용한다면, Printer 인스턴스가 아직 생성되지 않았을 때 동시에 여러 개의 스레드가 getPrinter()를 호출하고 인스턴스가 생성되었는지 확인합니다. 이 때, printer는 null인 상태입니다. 여러 개의 스레드가 동시에 확인을 하면, 당연히 null인 상태라고 확인하고 각 스레드는 새로운 인스턴스를 생성합니다. 이렇게 되면 한 개만 존재해야 하는 printer 인스턴스가 여러 개 발생할 수 있다는 점입니다.

4. Singleton의 문제점 해결 방법

정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법

public class Printer {
   // static 변수에 외부에 제공할 자기 자신의 인스턴스를 만들어 초기화
   private static Printer printer = new Printer();
   private Printer() { }
   // 자기 자신의 인스턴스를 외부에 제공
   public static Printer getPrinter(){
     return printer;
   }
   public void print(String str) {
     System.out.println(str);
   }
}

 위처럼 정적 변수에 바로 인스턴스를 초기화 해준다면, 클래스가 메모리에 로딩될 때 초기화가 한 번만 실행됩니다. 그리고 프로그램 시작부터 종료할 때까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있습니다.

인스턴스를 만드는 메소드에 동기화하는 방법

public class Printer {
   // 외부에 제공할 자기 자신의 인스턴스
   private static Printer printer = null;
   private int counter = 0;
   private Printer() { }
   // 인스턴스를 만드는 메서드 동기화 (임계 구역)
   public synchronized static Printer getPrinter(){
     if (printer == null) {
       printer = new Printer(); // Printer 인스턴스 생성
     }
     return printer;
   }
   public void print(String str) {
     // 오직 하나의 스레드만 접근을 허용함 (임계 구역)
     // 성능을 위해 필요한 부분만을 임계 구역으로 설정한다.
     synchronized(this) {
       counter++;
       System.out.println(str + counter);
     }
   }
}

 위처럼 getPrinter 메소드에 synchronized 키워드를 통해 여러 스레드가 동시에 getPrinter를 호출하는 것을 방지합니다. 다만 이러한 방법은, getInstance 메소드에 Lock을 하는 방식이라 문제가 발생하거나 시간이 오래 걸리면 속도가 많이 느려집니다.

728x90

'디자인 패턴' 카테고리의 다른 글

Factory Pattern  (0) 2020.05.06
Chain Of Responsibility  (0) 2020.05.02
Observer Pattern  (0) 2020.05.02
Template Method Pattern  (0) 2020.05.01
Facade Pattern  (0) 2020.04.03