싱글턴 패턴 (Singleton Pattern)

종명·2021년 4월 27일
0

헤드 퍼스트 디자인 패턴을 읽고 정리한 글입니다.

싱글턴 패턴 (Singleton Pattern)은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근 할 수 있도록 하기 위한 패턴입니다.

예제 코드

  1. 생성자를 private으로 선언해 Singleton 클래스에서만 클래스의 인스턴스를 만들 수 있도록 합니다.
  2. getInstance 메소드에서 인스턴스가 없다면 새로운 클래스 인스턴스를 만들어 리턴해줍니다.
public class Singleton {
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
            // 인스턴스가 필요할 때 만들어 집니다. (Lazy Instantiation)
            uniqueInstance = new Singleton(); 
        }
        return uniqueInstance;
    }
}

위 방법은 멀티스레딩 환경에서 문제가 있습니다.
만약 getInstance 메소드에 동시에 두 개의 스레드가 접근했다고 가정해봅시다.
1번 스레드가 인스턴스를 생성하기 직전에 2번 스레드가 if 문에 접근한다면 두 스레드 모두 새로운 인스턴스를 만들게 됩니다. 즉, 두 개의 인스턴스가 만들어지게 되어 문제가 생깁니다.

    public static Singleton getInstance() {
        if(uniqueInstance == null) { // 2번 스레드 접근
            // 1번 스레드 위치
            uniqueInstance = new Singleton(); 
        }
        return uniqueInstance;
    }

동기화 문제 해결

  1. getInstance 메소드에 synchronized 키워드를 추가해 동기화해줍니다. 이렇게 하면 1번 스레드가 해당 메소드안에 있을 때 2번 메소드는 접근할 수 없게 됩니다. (getInstance 메소드를 동시에 실행 할 수 없게 됩니다.)
    애플리케이션에 큰 부담을 주지 않는다면 위 방법도 괜찮은 방법입니다. 하지만 uniqueInstance 변수에 Singleton 인스턴스를 대입하고 나면 굳이 이 메소드를 동기화된 상태로 유지시킬 필요가 없습니다.
public class Singleton {
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(uniqueInstance == null) {
            uniqueInstance = new Singleton(); 
        }
        return uniqueInstance;
    }
}

  1. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버립니다.
    애플리케이션에서 반드시 Singleton의 인스턴스를 생성하고, 그 인스턴스를 사용한다면, 또는 인스턴스를 실행중에 수시로 만들고 관리하기가 성가시다면 다음과 같이 처음부터 Singleton 인스턴스를 만드는 것도 괜찮은 방법입니다.
public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        return uniqueInstance;
    }
}

  1. DCL (Double-Checking Locking) 을 써서 getInstace 메소드의 동기화 부분을 줄입니다.
    일단 인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화를 할 수 있습니다. 이렇게 하면 처음에만 동기화를 하고 uniqueInstance 변수에 Singleton 인스턴스가 대입된 후에는 동기화를 하지 않아도 됩니다.
public class Singleton {
    private volatile static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
       	    synchronized (Singleton.class) {
            	if(uniqueInstance == null) {
                	uniqueInstance = new Singleton(); 
                }
            }
        }
        return uniqueInstance;
    }
}

volatile 키워드가 필요한 이유는?

volatile은 Java 변수를 Main Memory에 저장하겠다는 것을 명시하는 것입니다.
만약 1번 스레드가 인스턴스를 생성하고 그 값이 Main Memory에 반영 되지 않고 CPU Cache에만 반영되었다면 2번 스레드가 인스턴스를 읽어올 때 동기화 문제가 생깁니다.
https://nesoy.github.io/articles/2018-06/Java-volatile

0개의 댓글