Computer Science/디자인패턴

[Head First Design Patterns] 05 싱글톤 패턴

계속지나가기 2020. 10. 20. 22:03
반응형

(본 강의 노트는 한빛 미디어의 [Head First Design Patterns]책을 기반으로 하고 있습니다)

05 싱글톤 패턴

Singleton Pattern

목적

  • 한 클래스는 하나의 인스턴스만을 가짐을 보장하고 해당 인스턴스에 어디서나 접근할 수 있도록 함
  • 자바에서 제공하는 Singleton Instance의 예
    • java.lang.Runtime.getRuntime()
    • java.lang.Desktop.getDesktop()
    • java.lang.System.getSecurityManager()

요소

설명

이름

싱글턴(Singleton)

문제

여러 객체가 생성되면 상태 관리가 어려움

해결방안

객체 생성자를 중앙 관리

결과

객체가 1개라서 일관된 상태

고전적 싱글턴 패턴 구현법

  • 고전적 싱글턴 패턴 구현 방법

    • private 디폴트 생성자 구현

      : singleton instance, 한 개의 객체만을 허용하기 위해

    • 싱글턴 인스턴스를 저장하는 정적 멤버 변수 생성

    • 싱글턴 인스턴스를 반환하는 정적 팩토리 메소드 구현

  • Multi-threads를 사용하는 프로그램에서는 문제가 될 수 있음

    : 해결버전은 뒤에서 설명

  • 클래스 다이어그램

  • 코드

public class Singleton {
  // Singleton 클래스의 유일한 인스턴스를 저장
  private static Singleton uniqueInstance;
  // 기타 멤버 변수  
  private Singleton() { }
  
  public static Singleton getInstance() {
    if (uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
  // 기타 메소드
}


 

사례 1 - 초콜릿 공장

  • 초콜릿 공장에서 초콜릿을 끓이는 장치를 컴퓨터로 제어
  • 이 보일러는 초콜릿, 우유를 받아서 끓이고 초코바를 만드는 단계로 넘겨줌
  • 초코홀릭사의 최신형 보일러를 제어하는 클래스를 보임
  • 코드
public class ChocolateBoiler {
  private boolean empty;
  private boolean boiled;
  public ChocolateBoiler() {
    empty = true; // 보일러가 비어있을 때만  동작
    boiled = false;
  }
  public boolean isEmpty() {
    return empty;
  }
  public boolean isBoiled() {
    return boiled;
  }
  public void fill() { 
    if (isEmpty()) { // 비어있을 때에만 재료 넣음
      empty = false;
      boiled = false;
      // 보일러에 우유/초콜릿 혼합 재료 넣음
  }
  // 보일러가 가득 차있고, 다 끓여진 상태에서만 보일러
  // 에 있는 재료를 다음 단계로 넘기고 보일러를 비움
  public void drain() {
    if (!isEmpty() && isBoiled()) {
      // 끓인 재료를 다음 단계로 넘김
      empty = true;
    }
  }
  // 보일러가 가득 차있고, 아직 끓지 않은 상태면 끓임
  public void boil() {
    if (!isEmpty() && !isBoiled()) {
      // 재료를 끓임
      boiled = true;
    }
  }
}

싱글턴 버전의 초콜릿 보일러 코드

public class ChocolateBoiler {
  private static ChocolateBoiler uniqueInstance;
  private boolean empty;
  private boolean boiled; 

  private ChocolateBoiler() {
    empty = true; // 보일러가 비어있을 때만  동작
    boiled = false;
  } 
 
  public static ChocolateBoiler getInstance() {
    if (uniqueInstance == null) {
      uniqueInstance = new ChocolateBoiler();
    }
    return uniqueInstance;
  }
  // 나머지 멤버 함수 코드
}

Thread-safe 버전의 싱글턴

  • 여러 개의 스레드에서 앞에서 작성한 코드가 사용되면 문제가 발생할 수 있음
  • 이를 해결하기 위해서는 getinstance() 함수에 동기화 시키는 코드를 넣어야 함
  • 코드
public class Singleton {
  private static Singleton uniqueInstance;

  private Singleton() { }
  public static synchronized Singleton getInstance() {
    if (uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
  // 나머지 멤버 함수 코드
}
  • 문제?

    : 비효율적(느려질 수 있음)

  • 해결 방법

    • getInstance()의 속도가 크게 영향 미칠 정도가 아니면 그냥 둠
    • 인스턴스를 필요할 때 생성하지 말고, 프로그램 시작될 때 생성
    • 코드
public class Singleton {
  private static Singleton inst = new Singleton();
  private Singleton() { }
  public static Singleton getInstance() {
    return inst;
  }
  // 나머지 멤버 함수 코드
}
  • volatile : 휘발성
    • 변수를 CPU cache에 저장하지 않고 메모리에서 읽고 저장
    • 쓰레드를 사용할 때 다른 프로세서에 있는 캐쉬에 변수값이 저장되어 서로 다른 값을 사용하는 것을 방지
  • Synchronized : 동기화

    : 여러 쓰레드에서 사용하려고 할 때 locking 메커니즘을 제공해서 한 번에 한 개의 쓰레드만 사용할 수 있도록 함

  • 코드

public class Singleton {
  private volatile static Singleton inst;
  private Singleton() { }
  public static Singleton getInstance() {
    if (inst == null) {
      synchronized (Singleton.class) {
        if (inst == null) {
          inst = new Singleton();
        }
      }
    }
    return inst;
  }
  // 나머지 멤버 함수 코드
}
  • DCL(Double-checking Locking)을 사용해서 getInstance()함수에서 동기화되는 부분을 줄이기

    : 인스턴스가 생성되어 있는지 확인 후, 생성되어 있지 않았을 때만 동기화를 시킬 수 있음

 

Another Solution

내부 정적 클래스 사용

  • JVM에서는 해당 싱글턴 클래스를 메모리에 적재할 때, 정적 멤버 변수가 없으므로 싱글턴 인스턴스를 생성하지 않음
  • getInstance()를 호출하면 내부에 있는 정적 클래스의 인스턴스를 생성해서 반환
  • 코드

 

반응형