순간을 기록으로

[JAVA] 싱글톤 패턴 2부 - 멀티쓰레드 환경에서 안전하게 구현하기 본문

Computer Science/DesignPattern

[JAVA] 싱글톤 패턴 2부 - 멀티쓰레드 환경에서 안전하게 구현하기

luminous13 2021. 12. 29. 14:15

안녕하세요. 

이번에는 멀티쓰레드 환경에서도 안정적인 다양한 싱글톤 패턴을 구현해보겠습니다.

 

가장 기본적인 싱글톤 패턴

public class Settings {
    
    private static Settings instance;	 
    private Settings() { }	// private 생성자
    public static Settings getInstance() {	// static 메소드
        if (instance == null) {
            instance = new Settings();
        }
        return instance;
    }
}

지난 포스팅 때 배운 가장 기본적인 싱글톤 패턴입니다. private 생성자를 이용해 외부에서 객체 생성을 막습니다. 직접 객체 생성대신 메소드를 통해 간접적으로 인스턴스를 제공해주는 static 메소드를 이용하는 방법입니다.

 

하지만 위의 싱글톤의 문제점은 멀티쓰레드 환경에서 안전하지 않다는 점입니다.

A쓰레드와 B쓰레드가 있다고 가정합니다. A쓰레드는 if (instance == null)을 지나고 instance = new Settings();코드를 실행하기 직전에 있다고 하고, B쓰레드는 if (instance == null) 코드를 실행 후라면 결국 2개의 인스턴스가 생성되는 문제점이 발생합니다.

 

멀티쓰레드 환경에서도 안전한게 싱글톤을 구현하는 다양한 방법이 있습니다.

 

1.동기화(synchronized)를 사용해서 메소드에 동시에 한 쓰레드만 접근하게 하기

public static synchronized Settings getInstance() {	// synchronized 키워드 사용
    if (instance == null) {
        instance = new Settings();
    }
    return instance;
}

위의 키워드를 사용하면 메소드에 동시에 2개 이상의 쓰레드가 동시에 메소드에 접근할 수 없게 됩니다. 따라서 멀티쓰레드에서도 안전한 싱글톤 패턴을 사용할 수 있습니다. 하지만 위의 방법은 동기화처리하는데 사용되는 락 매커니즘을 처리해야되서 성능상 불이익이 생깁니다. 이러한 락 매커니즘은 메소드를 호출 할 때마다 발생하니 생각보다 큰 불이익 입니다.

 

2.이른 초기화 (eager initialization)를 해서 애초에 인스턴스를 미리 만들기

public class Settings {

private static final Settings INSTANCE = new Settings();	// final도 추가
private Settings() {}
public static Settings getInstance() {
    return INSTANCE;
}
}

멀티쓰레드 환경에 안전한 두 번재 싱글톤 패턴입니다. 메소드가 호출될 때 인스턴스를 만드는 게 아닌 미리 인스턴스를 생성하는 방법입니다. static 변수는 객체가 생성되기 이전 클래스가 로딩될 때 생성됩니다. 그때 인스턴스도 생성됩니다. 이렇게 하면 동기화(synchroization)로 인한 성능상 불이익을 없앨 수 있습니다. 하지만 이 방법에도 문제점이 존재합니다. 만약 미리 인스턴스를 생성했지만 아예 이 인스턴스를 호출하지 않으면 이것 또한 자원 낭비입니다. 인스턴스를 생성하는 데에는 많은 자원(메모리 등)이 필요한 인스턴스라면 더욱 문제가 되겠죠?

 

3.double checked locking으로 효율적인 동기화 블록 만들어 메소드 안에서 두번 체크하기

public class Settings{

private static volatile Settings instance;	// volatile 키워드 사용해야 자바1.5 이상부터 동작가능
private Settings();

public static Settings getInstance() {
    if (instance == null) { // 체크 1
        synchronized (Settings.class) {	// 쓰레드 한개만 접근하도록 하기
            if (instance == null) { // 체크2
                instance = new Settings();
            }
        }
    }
    return instance;
}
}

다음과 같이 메소드 안에서 동기화(synchronized) 블록을 사용해서 블록 안에 쓰레드가 동시에 한개만 접근할 수 있도록 만들 수 있습니다. 예를들어 쓰레드A, B가 있는데 A는 객체를 생성하는 코드를 실행하기 전에 있고 B는 동기화 키워드로 인해 진입을 대기하고 있다고 합니다. 결국 A쓰레드는 인스턴스를 생성하고 B쓰레드는 기다리다 들어가니 if문 코드가 거짓이 나와 실행이 되지 않습니다. 이 방법은 인스턴스를 미리 만들지도 않고 메소드를 호출 할때마다 동기화 처리를 하지 않으므로 성능상 불이익도 없습니다. 인스턴스가 있는 경우에는 동기화 매커니즘이 사용되지 않습니다.하지만 조금 사용하기가 복잡한 단점이 있습니다.

 

4.static 이너 클래스를 사용하여 멀티쓰레드에 안전한 싱글톤 패턴 만들기

public class Settings {
private Settings() {}
private static class SettingsHolder {	// 이너 클래스 
    private static final Settings SETTINGS = new Settings();
}
public static Settings getInstance() {
    return SettingsHolder.SETTINGS;
}
}

이 방법은 3의 방법과 효과는 같지만 조금 덜 복잡한 방법이라 권장하는 방법 중 하나입니다.

static 이너 클래스를 사용해서 그 안에서 인스턴스를 생성하는 데요. 어떻게 보면 메소드 호출하기 이전에 미리 인스턴스를 생성하는게 아니냐고 물어보실 수 있습니다. 정확히는 getInstance()가 호출되어야 이너 클래스 SettingHolder가 로딩되고 그때  static 변수가 생성되고 초기화되기에 미리 인스턴스를 생성하는게 아니게 됩니다. 따라서 성능상 불이익도 존재하지 않습니다.

 


참고: 백기선의 디자인 패턴

'Computer Science > DesignPattern' 카테고리의 다른 글

[디자인패턴] 싱글톤 패턴 1부 자바  (0) 2021.12.28
Comments