싱글톤 패턴이란
싱글톤 패턴은 전체 시스템에서 단 하나의 인스턴스만 생성되도록 보장하고,
이 인스턴스에 대한 전역 접근을 제공하는 디자인 패턴입니다.
싱글톤 패턴의 구성
1. Client
싱글톤 인스턴스를 사용하는 주체
싱글톤 클래스의 getInstance() 메서드를 호출하여
싱글톤 인스턴스에 접근하고 이 인스턴스를 통해 필요한 작업을 수행
2. Singleton Class
싱글톤 패턴의 핵심이 되는 클래스
자기 자신의 유일한 인스턴스를 생성하고 이에 대한 접근 방법을 제공
싱글톤 패턴 다이어그램
예제를 바로 살펴볼건데, 싱글톤패턴 구현은 여러가지 방법이 있습니다.
각 방법이 장단점도 있고 취약점들도 존재합니다.
그중에 보편적인 방법 4가지를 살펴볼겁니다.
1. Lazy Initialization (레이지 초기화)
- 장점
객체가 필요할 때까지 생성을 지연시키므로, 리소스를 효율적으로 사용할 수 있습니다
-> 초기 로딩 시간을 단축시킬 수 있습니다.
- 단점
멀티스레딩 환경에서 동시에 getInstance()가 호출될 경우, 여러 인스턴스가 생성될 수 있는 문제가 있습니다.
-> 추가적인 동기화 처리가 필요합니다.
2. Thread Safe Singleton (스레드 세이프 싱글톤)
- 장점
synchronized 키워드를 사용하여 멀티스레딩 환경에서도 인스턴스가 단 하나만 생성되도록 보장합니다
- 단점
synchronized 키워드가 메서드 전체에 적용되므로, 매번 getInstance() 메서드를 호출할 때
-> 성능 저하가 발생할 수 있습니다.
3. Initialization-on-demand holder idiom (홀더로 초기화)
- 장점
JVM의 클래스 초기화 과정(클래스로더)이 동기화를 보장하기 때문에,
추가적인 동기화 없이도 스레드 안전을 유지할 수 있습니다.
-> 성능 저하 없이 지연 초기화를 실현할 수 있습니다.
- 단점
초기화 과정이 다소 복잡하게 느껴질 수 있으며, 이해하기 어려울 수 있습니다.
4. Enum Singleton
- 장점
직렬화, 스레드 안전성, 단일 인스턴스 보장 등으로 인해 구현이 간단하며, 리플렉션 공격에 대해서도 안전합니다.
- 단점
enum 타입은 확장할 수 없으며, 상속을 사용할 수 없습니다.
또한, 싱글톤 외의 용도로 사용하기 위한 추가적인 메서드나 필드를 정의하기에는 제약이 있을 수 있습니다.
패턴자체는 쉬워보이지만
실무에 적용할때 고려해야 할 사항이 많아서 사실 어렵습니다.
비지니스나 환경에 따라 구현이 틀려집니다.
예제 살펴볼게요
// 방법 1: Lazy Initialization Singleton
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
// 방법 2: Thread Safe Singleton
public class SingletonThreadSafe {
private static SingletonThreadSafe instance;
private SingletonThreadSafe() {
}
public static synchronized SingletonThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonThreadSafe();
}
return instance;
}
}
// 방법 3: Initialization-on-demand holder idiom Singleton
public class Singleton {
private Singleton() {
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
// 방법 4: Enum initialization 방식
public enum SingletonEnum {
INSTANCE;
}
// client
public class SingletonMain {
public static void main(String[] args) {
// 방법 1: Lazy Initialization 방식
SingletonLazy lazyInstance1 = SingletonLazy.getInstance();
SingletonLazy lazyInstance2 = SingletonLazy.getInstance();
System.out.println("Lazy Initialization Singleton HashCode 1: " + lazyInstance1.hashCode());
System.out.println("Lazy Initialization Singleton HashCode 2: " + lazyInstance2.hashCode());
System.out.println("Are both Lazy instances the same? " + (lazyInstance1 == lazyInstance2));
System.out.println();
// 방법 2: Thread Safe Singleton 방식
SingletonThreadSafe threadSafeInstance1 = SingletonThreadSafe.getInstance();
SingletonThreadSafe threadSafeInstance2 = SingletonThreadSafe.getInstance();
System.out.println("Thread Safe Singleton HashCode 1: " + threadSafeInstance1.hashCode());
System.out.println("Thread Safe Singleton HashCode 2: " + threadSafeInstance2.hashCode());
System.out.println("Are both Thread Safe instances the same? " + (threadSafeInstance1 == threadSafeInstance2));
System.out.println();
// 방법 3: Initialization-on-demand holder idiom 방식
Singleton holderIdiomInstance1 = Singleton.getInstance();
Singleton holderIdiomInstance2 = Singleton.getInstance();
System.out.println("Initialization-on-demand holder idiom Singleton HashCode 1: " + holderIdiomInstance1.hashCode());
System.out.println("Initialization-on-demand holder idiom Singleton HashCode 2: " + holderIdiomInstance2.hashCode());
System.out.println("Are both Holder Idiom instances the same? " + (holderIdiomInstance1 == holderIdiomInstance2));
System.out.println();
// 방법 4: Enum initialization 방식
SingletonEnum enumInstance1 = SingletonEnum.INSTANCE;
SingletonEnum enumInstance2 = SingletonEnum.INSTANCE;
System.out.println("Enum Singleton HashCode 1: " + enumInstance1.hashCode());
System.out.println("Enum Singleton HashCode 2: " + enumInstance2.hashCode());
System.out.println("Are both Enum instances the same? " + (enumInstance1 == enumInstance2));
}
}
실무사용 예
자바에 java.lang.Runtime 클래스가 떠오르네요.
Runtime.getRuntime() 메서드를 호출하여 이 클래스의 싱글톤 인스턴스에 접근할 수 있으며,
이를 통해 메모리 관리, 프로세스 실행 등의 작업을 수행할 수 있습니다.
결론
장점
1. 리소스 공유와 접근 제어: 전체 애플리케이션에서 단 하나의 인스턴스만 존재하기 때문에, 공유 리소스에 대한 접근을 효율적으로 관리하고 제어할 수 있습니다.
2. 메모리 사용량 감소: 인스턴스가 하나만 생성되므로, 동일한 객체를 여러 번 생성할 필요가 없어 메모리 사용량이 감소합니다.
단점
1. 글로벌 상태 문제: 싱글톤 인스턴스가 전역 상태를 가지게 되어 애플리케이션의 다른 부분과 강하게 결합될 수 있으며,
이는 코드의 유지보수와 테스트를 어렵게 만듭니다.
2. 멀티스레딩 문제: 잘못 구현된 싱글톤은 멀티스레딩 환경에서 동시성 문제를 일으킬 수 있습니다.
'디자인패턴' 카테고리의 다른 글
[디자인패턴-구조] 어댑터 패턴: 음악플레이어 예제를 통한 GOF 디자인 패턴의 이해 (WITH 자바) (0) | 2024.02.16 |
---|---|
[디자인패턴-생성] 프로토타입 패턴: 문서 템플릿 예제를 통한 GOF 디자인 패턴의 이해 (WITH 자바) (0) | 2024.02.12 |
[디자인패턴-생성] 추상팩토리 패턴: 가구 예제를 통한 GOF 디자인 패턴의 이해 (WITH 자바) (1) | 2024.02.12 |
[디자인패턴-생성] 팩토리메서드 패턴: 커피 예제를 통한 GOF 디자인 패턴의 이해(WITH 자바) (0) | 2024.02.10 |
[디자인패턴-생성] 빌더 패턴: 커피 예제를 통한 GOF 디자인 패턴의 이해 (0) | 2024.01.31 |