싱글톤(singleton) 패턴

하나의 인스턴스만 생성하고 관리하는 디자인 패턴이다.
두개 이상의 인스턴스가 존재해서는 안되거나 여러 인스턴스 생성을 막아
메모리 낭비를 줄이기 위해 사용한다.

예시

설정, 게임에서 설정창은 두개 이상은 필요없다.
DB Connection Pool, DB를 연결할때 매번 인스턴스를 생성하는것은 비효율적이다.

싱글톤 구현 방법 설명

간단한 싱글톤 구현

public class SimpleSingleton {
    private static SimpleSingleton instance;

    // 생성자를 private으로 선언해 더 이상 생성하지 못하도록 막는다
    private SimpleSingleton() {}

    // 클라이언트는 getInstance를 이용해서만 인스턴스에 접근할 수 있고, 인스턴스는 없을 경우에만 생성된다
    // 단 멀티스레딩 환경에서는 같은 인스턴스를 못받을 수도 있다.
    public static SimpleSingleton getInstance() {
        if(instance == null) {
            instance = new SimpleSingleton();
        }

        return instance;
    }
}

멀티스레딩에 안전하지만, Eager Loading으로 구현된 싱글톤 클래스

public class EagerThreadSafeSingleton {
    // static으로 메모리상에 하나의 인스턴스만 존재한다.
    // 단, 인스턴스 호출 여부에 상관없이 인스턴스가 메모리에 적재된다.
    private static final EagerThreadSafeSingleton INSTANCE = new EagerThreadSafeSingleton();

    private EagerThreadSafeSingleton() {}

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

멀티스레딩에 안전하며, Lazy Loading으로 구현된 싱글톤 클래스

public class LazyThreadSafeSingleton {
    // volatile은 메모리에 직접 읽고 쓰는 방식으로, 중간에 다른 연산이 실행되지 않는다(원자성)
    private static volatile LazyThreadSafeSingleton instance;

    private LazyThreadSafeSingleton() {}

    public static LazyThreadSafeSingleton getInstance() {
        if(instance == null) {
            // synchronized 블록을 이용해 객체 생성 부분을 제어한다.
            synchronized (LazyThreadSafeSingleton.class) {
                instance = new LazyThreadSafeSingleton();
            }
        }

        return instance;
    }
}

스레드에 안전하며, Lazy Loading을 구현하는 다른 방법

public class StaticInnerThreadSafeSingleton {
    // private을 이용해 인스턴스 생성을 막는다.
    private StaticInnerThreadSafeSingleton() {}

    // static inner 클래스를 이용해, Holder 클래스가 사용되는 경우에만 오직 하나의 인스턴스를 생성한다.
    // 멀티스레딩에 안전하며, Lazy Loading을 구현했다(인스턴스를 사용하는 경우에만 메모리 적재)
    private static class Holder {
        static final StaticInnerThreadSafeSingleton INSTANCE = new StaticInnerThreadSafeSingleton();
    }

    // getInstance 호출이 없으면 Holder의 INSTANCE는 생성되지 않는다.
    public static StaticInnerThreadSafeSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

단, 이 모든 방법들은 직렬화/역직렬화 또는 리플렉션을 이용하면 여러 인스턴스 생성이 가능하다.

직렬화 방법을 막기 위해서는 아래와 같이 사용한다.

Serializable 구현
readResolve 함수 오버라이드

직렬화/역직렬화로 새로운 인스턴스 막는 방법

public class LazyThreadSafeSingleton implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile LazyThreadSafeSingleton instance;

    private LazyThreadSafeSingleton() {}

    public static LazyThreadSafeSingleton getInstance() {
        if(instance == null) {
            synchronized (LazyThreadSafeSingleton.class) {
                instance = new LazyThreadSafeSingleton();
            }
        }

        return instance;
    }

    // 직렬화를 막기 위한 readResolve() 메서드
    protected Object readResolve() {
        return instance;
    }

}

리플렉션을 막는 방법은 enum을 활용하는 방법밖에 없다.

Enum을 활용한 싱글톤

public enum EnumSingleton {
    INSTANCE;
}

단, enum을 활용하면 상속이 불가능해진다.

예제는 이곳 에서 확인하실수 있습니다.