본문 바로가기
디자인 패턴

5. Singleton Pattern

by spaul 2023. 11. 13.

▣ What is Singleton Pattern?

Ensure a class only has one instance, and provide a global point of access to it.

 

 싱글턴 패턴은 특정 클래스에 대해서 한 개의 인스턴스만을 만들 수 있도록 하고, 어디서나 생성된 인스턴스에 접근할 수 있도록 하는 패턴입니다. 개인적으로는 디자인 패턴들 중에서 가장 단순하면서도 쉬운 패턴이라고 생각합니다.

 

 이렇게 싱글턴 패턴으로 만드는 이유는, 여러 객체가 생성되면 상태 관리에 있어 어려움을 겪는 경우가 발생하기 때문입니다. 따라서 객체 생성자를 중앙에서 관리하여 하나의 클래스에 대해 하나의 객체만 생성될 수 있도록 하여 객체의 상태를 일관되게 유지할 수 있습니다.

 

 

 

 고전적인 싱글턴 패턴 구현 방법으로 private 디폴트 생성자를 구현하는 방법이 있습니다.

public class Singleton {
    // Singleton 클래스의 유일한 인스턴스를 저장
    private static Singleton uniqueInstance;
    private Singleton() { }
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

 

 아주 간단한 싱글턴 인스턴스를 만드는 예시입니다. 생성자가 private로 설정되어 있기 때문에, new Singleton()을 통해서 생성자를 생성하는 것이 불가능합니다. 따라서 싱글턴 인스턴스를 반환하는 getInstance() 메서드를 통해 uniqueInstance가 생성되지 않았으면 새로운 싱글턴 인스턴스를 생성한 뒤 반환하고, 이미 생성되어 있으면 그냥 uniqueInstance를 반환하는 방식으로 싱글턴 패턴을 구현할 수 있습니다. 

 

 그런데 위와 같은 방식으로 싱글턴 패턴을 구현하는 것은 한 가지 문제점이 있는데, 멀티 쓰레드 환경에서 유니크 인스턴스 생성이 보장되지 않을 수 있다는 것입니다. 사실 이를 자세히 설명하기에는 내용이 너무 길어지기 때문에 간단하게만 설명하자면

 

 A 쓰레드에서 getInstance()의 조건문인 if (uniqueInstance == null) 조건을 확인한 직후 context switch가 발생해서 다시 B 쓰레드에서 getInstance()를 호출하여 uniqueInstance를 생성했다고 가정해봅시다. 그런 다음 다시 A 쓰레드로 돌아왔는데, A 쓰레드는 이미 if 조건문까지 확인한 상태이므로 다음 단계인 uniqueInstance를 생성하게 됩니다. 결과적으로 uniqueInstance가 두 개 생성되는 결과가 발생하게 됩니다.

 

 물론 이를 방지하는 방법이 있습니다. 우선 아래와 같은 방식으로 쓰레드 동기화를 위한 코드를 넣는 방법이 있습니다.

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() { }
    // synchronized 추가
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
// 나머지 멤버 함수 코드
}

 

 이전과 다르게 synchronized 코드가 추가 된 것이 보이시나요? 이렇게 하면 멀티 쓰레드 환경에서도 안정적으로 싱글턴 인스턴스를 생성할 수 있습니다. 하지만 이는 singleton 인스턴스가 반환될 때까지 context switching을 막는 방법이므로 만약 코드가 길어진다면 프로그램이 느려질 수 있습니다. 물론 위와 같이 짧은 코드라서 프로그램 실행에 별 영향을 줄 정도가 아니라면 위와 같은 방식으로 구현해도 괜찮습니다. 

 

public class Singleton {
    private static Singleton inst = new Singleton();
    private Singleton() { }
    public static Singleton getInstance() {
        return inst;
    }
// 나머지 멤버 함수 코드
}

 

 다른 방식으로는 런타임에 인스턴스가 필요할 때 생성하는 것이 아닌, 프로그램이 시작되면 일단 생성해놓는 것입니다. 이렇게하면 싱글턴 인스턴스를 생성하느라 프로그램 실행 중간에 지연되는 일을 걱정하지 않아도 됩니다. 하지만 싱글턴 인스턴스를 생성하는데 시간이 소요되므로 프로그램을 실행하기 위해 로딩하는 시간이 좀 더 걸릴 것입니다. 개인적으로는 이와 같은 방식이 가장 간단하고 깔끔하다고 생각하여 선호합니다.

 

public class Singleton {
    // inner static class
    private static class InnerSingleton {
        static final Singleton inst = new Singleton();
    }
    private Singleton() { }
    public static Singleton getInstance() {
        return InnerSingleton.inst;
    }
}

 

 또는 이런식으로 싱글턴 패턴 내부에 또다른 내부 정적 클래스를 만들어서, 외부 에서는 InnerSingleton 클래스에 접근할 수 없도록 한 뒤 내부 정적 클래스에 싱글턴 인스턴스를 생성한 뒤 필요시 이 인스턴스를 반환하는 방법도 있습니다. 어떤 방법을 사용하더라도 동일하게 작동되므로, 필요에 따라 싱글턴 인스턴스를 생성하는 방식을 선택하면 될 것 같습니다.

 

References

[1] 조용주, 고급객체지향프로그래밍

[2] Eric Freeman, Head Frist Design Patterns

'디자인 패턴' 카테고리의 다른 글

7. Decorator Pattern  (0) 2023.11.28
6. Iterator Pattern  (0) 2023.11.14
4. Observer Pattern  (0) 2023.11.07
3. Strategy Pattern  (0) 2023.10.29
2-2. SOLID(S.O.L.I.D)  (0) 2023.10.18