Singleton 구현 방법과 Thread Safe (Java)

Singleton 구현 방법과 Thread Safe (Java)

프로세스 전역에서 유일한 객체

1. Normal

가장 간단하게 구현할 수 있는 방법이다.

그러나 이 방법은 Thread Safe 하지 않다. Multi-Thread 환경에서 instance가 유일하지 않고 여러 개 생성될 가능성이 있다.

public class Settings { private static Settings instance; // 외부에서 new로 인스턴스를 생성하지 못하게 막는다. private Settings() {} public static Settings getInstance() { if (instance == null) { instance = new Settings(); } return instance; } }

public class App { public static void main(String[] args) { Settings settings1 = Settings.getInstance(); Settings settings2 = Settings.getInstance(); System.out.println(settings1 == settings2); // true } }

2. Using synchronized keyword (Lazy Initialization)

Lazy initialization

Normal 방식의 문제점인 Multi-Thread 환경에서 가장 단순하게 대응하는 방법이다. getInstance() 메서드에 lock을 걸어 Thread Safe를 보장한다. 하지만 동기화 처리로 인해 성능 이슈를 야기할 수 있다.

public class Settings { private static Settings instance; private Settings() {} // 한 번에 하나의 Thread만 접근할 수 있도록 메서드를 수정 public static synchronized Settings getInstance() { if (instance == null) { instance = new Settings(); } return instance; } }

3. Eager initialization with final keyword

Eager initialization

프로세스 구동 시점에서 미리 instance를 생성하는 방법이다. 얼핏 간단하고 제일 나을 수 있지만, 만약 해당 Singleton Instance가 잘 사용되지 않는다면 메모리만 낭비할 뿐이다.

public class Settings { // static final 상수로 미리 생성 private static final Settings INSTANCE = new Settings(); private Settings() {} public static Settings getInstance() { return INSTANCE; } }

4. Double-checked locking with volatile keyword

Lazy initialization + Double-checked locking

Java 1.5 이상부터 사용할 수 있다. keyword는 이전부터 존재했으나, 여기서 말하는 Thread Safe를 위해서는 1.5 버전부터 사용할 수 있다. synchronized, Eager initialization 방식의 두 단점을 보완한 방법이다. 하지만 그 자체로 구현을 이렇게까지 해야 하나 싶은 단점이 있다.

public class Settings { // volatile 키워드를 붙여주어야만 실질적으로 동작한다(Java 1.5 이상). private static volatile Settings instance; private Settings() {} public static Settings getInstance() { if (instance == null) { // 메서드 자체에 lock이 걸리지 않고 블럭으로 관리한다. synchronized (Settings.class) { if (instance == null) { instance = new Settings(); } } } return instance; } }

5. Static inner class (Holder)

Initialization on demand Holder idiom

Thread Safe를 보장함과 동시에 구현의 편리성을 위한 방법이 다행히 존재한다. inner class를 통해 field를 관리하는 방법이다. inner class은 Eager initialization 방식으로 구현해 JVM에 매커니즘에 따라 알아서 Thread Safe를 보장하게끔 하는 방식으로 추측된다.

public class Settings { private Settings() {} // static inner class private static class SettingsHolder { private static final Settings INSTANCE = new Settings(); } public static Settings getInstance() { return SettingsHolder.INSTANCE; } }

6. Enum initialization

volatile과 마찬가지로 1.5 버전부터 사용할 수 있는 enum class를 활용한 방법이다. JVM 구동 시 static 처럼 한 번 초기화 되는 점을 이용한 방식이다.

public enum EnumInitialization { INSTANCE; static String test = ""; public static EnumInitialization getInstance() { test = "test"; return INSTANCE; } }

Conclusion

갑자기 Front End 개발자가 돼버리는 바람에 Java에 손 뗀 지 벌써 반년은 넘은 듯 하다. 그래도 개발의 본질을 잃지 않으려고 TDD, Architecture, 그리고 Design Patterns 등을 꾸준히 학습하려고 하는데, 아직도 22개 패턴이 남았다. 아득해라.

References

from http://jihogrammer.tistory.com/102 by ccl(A) rewrite - 2021-12-29 12:01:33