싱글톤( Singleton Pattern ) 패턴이란?


싱글톤 패턴은 인스턴스를 만드는 절차를 추상화시키는 생성 패턴 중의 하나입니다.

싱글톤 패턴은 클래스의 인스턴스를 하나만 생성하고, 생성된 하나의 인스턴스에 접근하는 방법을 제공합니다.

인스턴스가 딱 하나만 있어도 되는 것들로는 Factory, Thread Pool, Connection Pool, Logger 등이 있습니다.

 

 

 

싱글톤 패턴의 구현 방법


싱글톤 패턴을 구현하는 방법은 몇 가지가 있는데, 가장 심플한 방법부터 차츰 약점을 보완하는 식으로 알아보겠습니다.

다음은 대중적으로 가장 많이 알려진 구현 방법입니다.

public class Singleton {

    private static final Singleton instance = new Singleton();
    
    private Singleton() {} // 생성자를 private으로 선언하여 외부에서의 생성을 막는다.
    
    public static Singleton getInstance() {
        return instance;
    }
}

 

위 구현 방법의 문제점은 무엇일까요?

--> 인스턴스의 사용여부와 관계없이 클래스 로딩 시점에 인스턴스를 생성합니다.

       만약에 해당 클래스가 엄청 큰 클래스이고 지금 당장 사용할 일이 없는데 미리 생성되버리면 메모리 낭비일 것입니다.

 

다음은 위를 보완한 두 번째 구현 방법으로 getInstance() 메서드의 호출시점에 인스턴스를 생성합니다.

저는 이 방법으로 싱글톤을 처음 배웠는데, 싱글톤 구현 방법이 이게 끝인줄 알았습니다... 😅

public class Singleton {

    private static Singleton instance;
    
    private Singleton() {} // 생성자를 private으로 선언해 외부에서의 생성을 막는다
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

그럼 위 구현 방법의 문제점은 무엇일까요?

--> 바로 multi-thread 환경에서의 동기화 문제입니다.

       만약 여러 개의 스레드가 동시에 getInstance() 메서드를 호출해 if문에 동시에 들어가면 인스턴스가 여러 개 생성되어지는 불상사가         발생할 수 있습니다.

 

다음은 위를 보완한 구현 방법으로 syncronized 키워드로 getInstance() 메서드를 임계 영역(Critical Section)으로 설정합니다.

public class Singleton {
    
    private static Singleton instance;
    
    private Singleton() {} // 생성자를 private으로 선언해 외부에서의 생성을 막는다.
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

동기화 문제까지 해결했으니 끝일까요?

--> 아닙니다. 사실 따지고 보면, getInstance() 메서드는 처음 호출될 때 말고는 동기화가 필요 없습니다.

       syncronized 키워드로 임계 영역을 만드는 비용이 큰데 매번 getInstance()를 호출할 때마다 이런 작업을 하는건 비효율적입니다.

 

다음은 위 문제점을 보완해 최초 한 번만 동기화를 하도록 구현한 방법입니다. 

이를 Double-Checking Locking 기법이라고 부릅니다.

public class Singleton {
    
    private static Singleton instance;
    
    private Singleton() {} // 생성자를 private으로 선언해 외부에서의 생성을 막는다.
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

이제 더 이상 문제가 없을까요?

--> 네, 모든 문제는 해결했습니다. 다만.. 코드가 많이 지저분해졌습니다.

 

인스턴스를 사용시점에 생성해서 메모리 낭비를 막고,
multi-thread 동기화 문제와 이때의 성능 문제를 해결하며,
코드 또한 깔끔하게 짜는 방법은 없을까요? 🧐

 

네, 있습니다. (세상에는 정말 똑똑한 사람들이 많은 거 같습니다 👏 )

다음은 위 모든 문제를 해결한 마지막 구현 방법으로, 현재 가장 널리 쓰이는 구현 방법입니다.

public class Singleton {

    private Singleton() {} // 생성자를 private으로 선언해 외부에서의 생성을 막는다.

    private static class SingletonHelper {
        private static final Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHelper.instance;
    }
}

 

 

 

참고자료


[1] readystory.tistory.com/116?category=822867