책너두 (헤드 퍼스트 디자인 패턴) 15일차 (~216p)

요약

  • 싱글턴 패턴
    • 고전적인 싱글턴 패턴 구현법
    • 초콜릿 보일러 코드 살펴보기
    • 싱글턴 패턴의 정의
    • 초콜릿 보일러에 문제 발생
    • 멀티스레딩 문제 살펴보기
    • 멀티스레딩 문제 해결하기
    • 더 효율적으로 멀티스레딩 문제 해결하기
      • getInstance()의 속도가 그리 중요하지 않다면 그냥 둔다.
      • 인스턴스가 필요할 때는 생성하지 말고 처음부터 만든다.
      • ‘DCL’을 써서 getInstance()에서 동기화되는 부분을 줄인다.

메모

CHAPTER 05. 하나뿐인 특별한 객체 만들기 : 싱글턴 패턴

  • 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해 주는 패턴
    • ex) 스레드 풀, 캐시, 대화상자, 사용자 설정, 레지스트리 설정 처리, 로그 기록용, 디바이스 드라이버
  • 객체 인스턴스를 어디서든지 액세스할 수 있게 만듦.
  • 전역 변수 쓸 때의 여러 단점을 감수할 필요가 없음.
    • 전역 변수는 한 번도 사용하지 않는다면 괜히 자원만 잡아먹지만, 싱글턴 패턴을 사용하면 필요할 때만 객체를 만들 수 있음.

고전적인 싱글턴 패턴 구현법

public class Singleton {
    private static Singleton uniqueInstance;
    // 기타 인스턴스 변수

    private Singleton() {}

    public static Singleton getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

// 기타 메소드
  • Singleton 클래스의 하나뿐인 인스턴스를 저장하는 정적 변수 → uniqueInstance
  • 생성자를 private 으로 선언했으므로 Singleton 에서만 클래스의 인스턴스 생성 가능
    • 인스턴스가 필요한 상황이 닥치기 전까지 아예 인스턴스를 생성하지 않음.
    • ‘게으른 인스턴스 생성(lazyinstantiation)’ 이라고 부름
  • getInstance() 메소드를 통해 클래스의 인스턴스를 반환함.

초콜릿 보일러 코드 살펴보기

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

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    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 boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}
  • 이 초콜릿보일러 인스턴스가 2개가 따로 생기면 상당히 안 좋은 상황이 일어날 수 있음.

싱글턴 패턴의 정의

📍 싱글턴 패턴(Singleton Pattern) : 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공함.

  • uniqueInstance 클래스 변수에 싱글턴의 하나뿐인 인스턴스가 저장됨.

초콜릿 보일러에 문제 발생

  • fill() 메소드에서 아직 초콜릿이 끓고 있는데 새로운 재료를 넣어서 500갤런이나 되는 우유와 초콜릿 흘러넘침..
    • 멀티스레드를 사용하도록 초콜릿 보일러 컨트롤러를 최적화 시켰다고함.

멀티스레딩 문제 살펴보기

ChocolateBoiler boiler = ChocolateBoiler.getInstance();
boiler.fill();
boiler.boil();
boiler.drain();
  • 동시에 두 스레드가 위와 같은 코드를 실행한다면, 서로 다른 2개의 객체가 리턴될 수 있음.

멀티스레딩 문제 해결하기

public class Singleton {
    private static Singleton uniqueInstance;
    // 기타 인스턴스 변수

    private Singleton() {}

    public static synchronized Singleton getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

// 기타 메소드
  • synchronized 키워드를 추가하여, 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 기다리게 함.
  • 동기화가 꼭 필요한 시점은 이 메소드가 시작할 때임.
    • uniqueInstance 변수에 Singleton 인스턴스를 대입하는 순간 부터, 굳이 이 메소드를 동기화된 상태로 유지할 필요가 없음
    • 동기화는 불필요한 오버헤드만 증가시킬 뿐임.

더 효율적으로 멀티스레딩 문제 해결하기

방법 1. getInstance()의 속도가 그리 중요하지 않다면 그냥 둔다.

  • getInstance()를 동기화하는 게 그리 어려운 일도 아니고, 효율도 좋을 수 있음.
  • 하지만, 메소드를 동기화하면 성능이 100배 정도 저하됨.
  • 만약 getInstance() 메소드가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해야 함.

방법 2. 인스턴스가 필요할 때는 생성하지 말고 처음부터 만든다.

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return uniqueInstance;
    }
}
  • 정적 초기화 부분(static initializer)에서 Singleton의 인스턴스를 생성함 → 스레드를 써도 별 문제 없음
    • 클래스가 로딩될 때, JVM에서 Singleton의 하나뿐인 인스턴스를 생성해 줌
  • 인스턴스가 이미 있기 때문에 getInstance() 메소드는 그냥 리턴만 하면 됨.

방법 3. ‘DCL’을 써서 getInstance()에서 동기화되는 부분을 줄인다.

  • DCL(Double-Checked Locking)을 사용하면 인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화할 수 있음.
    • 처음에만 동기화하고 나중에는 동기화하지 않아도 됨.
public class Singleton {
    private volatile static Singleton uniqueInstance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (uniqueInstance == null) { // 인스턴스가 있는지 확인하고 없으면 동기화된 블록 실행
            synchronized (Singleton.class) { // 처음에만 동기화 됨
                if (uniqueInstance == null) { // 블록에서 다시 한 번 변수가 null 인지 확인한 다음
                    uniqueInstance = new Singleton(); // 인스턴스 생성
                }
            }
        }
        return uniqueInstance;
    }
}
  • volatile 키워드를 사용하면 멀티스레딩을 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행됨.
  • 싱글턴을 구현하면, getInstance() 메소드를 사용할 때 발생하는 속도를 극적으로 줄일 수 있음

DCL은 자바 1.4 이전 버전에서는 쓸 수 없음.

댓글

Designed by JB FACTORY