Java & Spring

[JAVA] Singleton Pattern (싱글톤 패턴)

ju_young 2023. 12. 2. 16:05
728x90

Singleton Pattern (싱글톤 패턴)은 특정 클래스에 대해서 객체 인스턴스가 하나만 만들어질 수 있도록 해주는 패턴이다.

대신 전역 변수를 사용하게되면 애플리케이션이 시작될 때 생성되고 사용하지 않을 경우 자원만 잡아먹는 꼴이 된다. 하지만 싱글톤 패턴은 필요할 때만 객체를 만들 수 있다.

기본 구현

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}

    public static Singleton getInstance() {
        //인스턴스가 생성되지 않았다면 생성
        if (uniqueInstance  null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

멀티 스레딩의 동기화 문제

기본 구현 코드의 if (uniqueInstance null) { 라인에서 context switching이 일어나면 인스턴스가 여러 개 생성될 수 있다.

thread1 thread2
if (uniqueInstance null) {  
  if (uniqueInstance null) {
uniqueInstance = new Singleton();  
  uniqueInstance = new Singleton();

즉, thread1과 thread2 모두에서 인스턴스가 생성되지 않았다고 판단하여 객체를 새로 생성하게 되는 것이다.

이런 동기화 문제를 해결하기위해서 synchronized 키워드를 사용할 수 있다.

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}

    //한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 기다려야 한다.
    public static synchronized Singleton getInstance() {
        if (uniqueInstance  null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

이로 인해 두 스레드가 메소드를 동시에 실행시킬 수 없게 된다.

동기화의 속도 문제

일단 uniqueInstance 변수에 Singleton 인스턴스를 대입하고 나면 이후에는 동기화된 상태로 유지할 필요가 없다. 다시말해 인스턴스를 생성하는 첫 번째 과정을 제외하면 동기화가 필요없다.

1. 애플리케이션에 큰 부담을 주지 않는다면 그냥 둔다.

2. 인스턴스를 처음에 만들어 버린다.

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        return uniqueInstance;
    }
}

이 방법은 인스턴스를 항상 사용할 경우 고려할 수 있는 방법이다. 사용하지 않을 경우 자원만 차지하게 된다.

3. DCL (Double-checking Locking)

DCL을 사용하면 인스턴스가 생성되어 있지 않았을 때만 동기화를 할 수 있다.

public class Singleton {
    private volatile static Singleton uniqueInstance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        //인스턴스가 있는지 확인하고 없으면 동기화 블록으로 진입
        if (uniqueInstance  null) {
            synchronized (Singleton.class) {
                //동기화 블록으로 들어간 후에도 null 체크
                if (uniqueInstance  null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}
  • 동기화 블록 안에서도 null 체크를 하는 이유는 다른 스레드에서 이미 인스턴스를 생성했을 수도 있기 때문이다.
  • volatile
    • Main Memory에 저장하겠다는 것을 명시한다.
    • 멀티스레드 환경에서 하나의 스레드만 read&write하고 나머지 스레드가 read하는 상황에서 가장 최신의 값을 보장한다.
    • CPU Cache 보다 비용이 더 큰 Main Memory에 저장되어 사용되기 때문에 성능에 영향을 줄 수 있다. 따라서 인스턴스의 캐싱을 고려해볼 수 있을 것 같다.

 

[reference]
Head First Design Patterns
http://tutorials.jenkov.com/java-concurrency/volatile.html

728x90