본문 바로가기
디자인패턴

단일체 패턴 (Singleton Pattern)

by 이석준석이 2021. 6. 5.

사용의도

  • 단 하나의 인스턴스만이 필요하고, 이를 접근하는 경우 사용합니다.

생성방식

 

1. Eager Initialization

  • 바이트코드(.class파일) -> native code(JVM이 실행할 수 있는 코드) 로 변환하는 과정에서
    • ClassLoader 가 클래스를 로딩 할 때, Initializing 과정에서 static 변수가 할당되고, static 블록이 수행되는 것을 이용하여 구현합니다. 참고
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

 

코드 설명
private static final Singleton INSTANCE = new Singleton(); Initializing 과정에서 한번만 수행되므로, 이때 인스턴스를 생성합니다.
private Singleton(){} 외부에서 Singleton 객체를 생성할 수 없도록 막습니다.
public Singleton getInstance() 한번만 생성한 인스턴스를 외부에서 가져다가 사용할 수 있도록 합니다.

2. Lazy Initialization

  • 싱글톤 인스턴스를 생성하는 과정에서 여러 쓰레드에서 중복되어 한 시점에 인스턴스를 생성하는 것을 항상 조심해야합니다.
  • 따라서 아래와 같은 방식으로 생성합니다.

2.1. Lazy Initialization with synchronized block

  • 자바에서 mutex 접근을 막기위해 사용되는 synchronized keyword 를 사용하여 멀티쓰레드환경에서 해당 블록에 하나의 쓰레드만 접근할 수 있도록 제한하는 방식으로 구현합니다.
public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {}

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

단점

  • 인스턴스를 생성한 이후에도 getInstance() 메소드를 호출할 때마다 하나의 쓰레드만 접근할 수 있으므로 성능에 좋지않습니다.

2.2. Lazy Initialization, Double checking locking

  • null 이 아닌경우에는 synchronized 블록을 거치지 않고 바로 리턴하기위해 사용됩니다.
public class Singleton {
    private volatile static Singleton INSTANCE;

    private Singleton() {}

    public static Singleton getInstance() {
        if(INSTANCE == null) {
            synchronized (Singleton.class) {
                if(INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

 

volatile keyword

  • Java 변수를 Main Memory 에 저장하겠다는 것을 명시하는 것입니다.
  • 매번 변수의 값을 Read 할 때마다 CPU Cache 에 저장된 값이 아닌 Main Memory 에서 읽습니다.
  • 변수의 값을 Write 할 때마다 Main Memory 에 까지 작성하는 것입니다.
    • 마치 Cache 쓰기 정책의 Write Through 같은 느낌입니다.

2.3. Lazy Initialization, Enum

  • Enum 은 
    • static constant 입니다.
    • 하지만 compile time constant 는 아닙니다.
  • Enum 클래스는 처음 사용하는 시점에 ClassLoader 에 의해서 로딩됩니다.
    • 그 이후에는 더이상 사용해도 로딩이 되지 않으므로, Thread-safe 합니다.
public enum Singleton {
    INSTANCE;
}

2.4. Lazy Initialization, Lazy Holder

  • static 변수나 블럭은 JVM에 의해서 ClassLoader 가 Initialization 과정에서 수행하거나 할당하지만
  • static InnerClass 의 경우에는 Enum 과 마찬가지로 처음 사용하는 시점에 한번만 ClassLoader 에 의해서 로딩됩니다.
    • 해당 원리를 이용해서 Eager Initialization 과는 다르게 사용시점에 한번만 로딩하도록 구현합니다.
public class Singleton {
    private Singleton() {}

    private static class InnerClass {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() { // <-- 해당 메소드가 처음 수행될 때 ClassLoader 에 의해서 로딩됩니다.
        return InnerClass.INSTANCE; 
    }
}

참고