Singleton

싱글톤 패턴(Singleton Pattern)은 대부분의 디자인 패턴 책의 첫 챕터에 등장합니다. 그 만큼 일반적인 대중적이고 가장 잘 알려진 이 패턴을 알아보도록 하겠습니다.

싱글톤 패턴은 기본적으로 Application 상에서 단 한개의 Instance만 필요할 경우 사용됩니다. 동일한 Instance가 여러개가 존재한다면 그 만큼 Memory 영역을 많이 잡아먹게 될 것이고 이것은 곧 성능 저하로 이어집니다. 그렇기에 싱글톤 패턴을 이용하여 Instance를 메모리 영역상에 하나만 존재하게 하여 사용할 수 있는 생성 패턴입니다.

Singleton Pattern의 구현

싱글톤 패턴을 구현할 때 일반적으로 생성자를 Private으로 지정하는 경우가 많습니다. 그 이유는 Instance를 생성할 때 생성자를 활용하는 것이 아닌, Instance 생성을 담당하는 Method로 Instance의 개수를 관리하는 방식을 사용하기 때문입니다. 이를 기반으로 기본적인 예제를 보겠습니다.

// Example 1
class Singleton {
      private int field;
      private static Singleton instance;

    private Singleton(int field) {
        this.field = field;
    }

    public static Singleton getInstance(int field) {
        if (this.instance == null) {
            this.instance = new Singleton(field);
        }
        return instance;
    }
}

해당 예제에서 getInstance Method가 생성자를 대신하여 Instance를 생성하는 역할을 담당합니다. 기존 생성자는 Private으로 선언이 되어있고 getInstance만을 사용하여 Instance를 생성할 수 있습니다. getInstance를 실행하면 먼저 static으로 선언되어 있는 instance 필드가 null인지, 즉 이미 Instance가 생성된 이력이 있는지를 확인한 뒤 생성된 적이 없다면 instance 필드에 Instance를 생성한 뒤 반환합니다. 두번째 호출 부터는 instance 필드에 값이 존재하는 상태이니 instance를 그대로 반환합니다.

첫번째 개선

Example 1을 Thread에서 사용할 때 문제가 발생할 수 있습니다. 다수의 Thread가 getInstance를 호출할 때 순차적으로 실행되는 것이 아니라 동시에 호출되기 때문에 Thread마다 첫번째 호출을 일으켜 여러개의 객체가 생성될 수 있습니다. 이는 아래 예제와 같이 해결할 수 있습니다.

// Example 2
class Singleton {
    private int field;
    private static Singleton instance;

    private Singleton(int field) {
        this.field = field;
    }

    public synchronized static Singleton getInstance(int field) {
        if (this.instance == null) {
            this.instance = new Singleton(field);
        }
        return instance;
    }
}

syncronized를 이용하면 여러 Thread에서 동시에 getInstance를 호출하지 않고 순차적으로 이전 Thread가 호출을 끝낸 뒤에 실행을 하니 문제를 해결할 수 있습니다.

두번째 개선

Example 2의 경우에 getInstance를 통해 Instance를 생성할 떄 마다 if (this.instance == null)을 거쳐야하는 Overhead가 발생합니다. 또한 값비싼 syncronized 키워드를 사용합니다. 이 두가지 문제를 한번이 해결할 수 있는 개선 방안이 있습니다.

// Example 3
class Singleton {
    private int field;
    private static Singleton instance = new Singleton(5);

    private Singleton(int field) {
        this.field = field;
    }

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

instance 필드가 선언되는 동시에 instance를 생성합니다. 이러한 방법으로 getInstance가 첫번째로 호출되는지를 확인할 필요가 없어집니다. 다만, 이 경우 생성자를 통한 필드 초기화를 수행할 수 없다는 단점이 있습니다.