도찐개찐
[코드디자인패턴-JAVA] Singleton Pattern 본문
Singleton Pattern(싱글톤 패턴)
사용시점:
- 단일 인스턴스 필요시: 로그 파일 작성기, 드라이버 객체, 캐시, 스레드 풀 등과 같이 전체 시스템에서 단 하나의 인스턴스만 유지 되어야 하는 경우에 사용
- 자원 공유: 여러 클라이언트가 동일한 리소스에 엑세스 해야 하는 경우에 싱글톤 패턴이 유용
- 비용 절감(재사용): 객체를 생성하는 데 비용이 많이 드는 경우, 싱글톤 패턴을 사용해 한번만 생성하고 재사용
장점:
- 인스턴스 제어: 인스턴스가 한 개만 생성 되는 것을 보장하므로, 중복 생성으로 인한 리소스 낭비 방지
- 전역 엑세스 포인트 제공: 싱글톤 객체는 애플리케이션 내에서 쉽게 엑세스 할 수 있는 전역 엑세스 포인트를 제공
- 자원 공유: 동일한 객체를 여러 클라이언트와 공유 할 수 있으므로, 자원을 효율적으로 사용
단점:
- 멀티스레드 이슈: 멀티스레드 환경에서 동시에 여러개의 인스턴스가 생성 될 수 있습니다. 이를 방지하기 위한 동기화 메커니즘이 필요하며 이는 성능 저하 가능성이 있음
- 결합도 증가: 전역 인스턴스로써, 다른 클래스들과 과도하게 결합될 가능성이 있습니다. 이로 인해 코드 유지보수와 테스트가 어려워 질 수 있습니다.
- 스케일링 이슈: 인스턴스가 한 개만 존재해야 하므로, 확장성 떨어집니다. 따라서 싱글톤 패턴을 적절하게 사용하지 않으면 소프트웨어 시스템 전체에 부정적인 영향을 끼칠 수 있습니다.
결론
- 이런 장단점을 잘 고려하여 싱글톤 패턴을 적절한 상황에 사용하는 것이 중요합니다. 특히 멀티스레드 환경에서의 안전성과 객체 간의 느슨한 결합을 유지하는 것이 중요합니다.
- Eager Initialization(즉시초기화):
- 장점
- 클래스가 로딩 될 때 인스턴스를 생성하는 방법
- 생성비용이 낮고 멀티 쓰레드 환경에서 안전
- 클래스 로딩 시점에 인스턴스가 생성되므로, 불필요 리소스 낭비
- 단점
- 리소스 낭비: Eager Initialization는 프로그램 시작 시 모든 리소스를 초기화합니다. 그런데 이들 리소스 중 실제로 사용되지 않는 것들이 있다면, 그 초기화는 시간과 메모리 자원의 낭비가 됩니다.
- 시작 지연: 초기화에 시간이 많이 걸리는 리소스가 있다면, 그 리소스의 Eager Initialization은 프로그램 시작을 지연시키는 원인이 될 수 있습니다.
- 초기화 순서 문제: 객체 간의 종속성이 있을 때, 초기화 순서를 정확히 관리해야 합니다. 초기화 순서가 잘못되면 런타임 에러를 발생시킬 수 있습니다.
- Lazy Initialization이 이런 문제를 해결할 수 있지만,
- 그 자체의 문제점(동시성 문제, 복잡성 증가 등)도 있습니다.
- 따라서 적절한 초기화 전략을 선택하는 것이 중요합니다.
- 경우에 따라서는 두 방식을 적절히 혼합하는 것이 최선의 방법일 수도 있습니다.
public class EagerInitializationSingleton {
private static final EagerInitializationSingleton instance =
new EagerInitializationSingleton();
private EagerInitializationSingleton() {}
public static EagerInitializationSingleton getInstance() { return instance; }
public String hello() { return "hello, Eager Initialization"; }
}
// ------------ Main ----------
public class EagerMain {
public static void main(String[] args) {
EagerInitializationSingleton eager = EagerInitializationSingleton.getInstance();
System.out.println(eager.hello());
}
}
EagerMain.main(null);
hello, Eager Initialization
- LazyInitialization(지연초기화)
- 장점
- 인스턴스를 필요한 시점에 생성 함으로 애플리케이션 시작시에 리소스를 사용하지 않음.
- 단점
- 멀티스레드 환경에서의 문제: 여러 스레드가 동시에 초기화를 시도하면, 한 번에 하나의 인스턴스만 생성되어야 하는 것이 두 개 이상 생성될 수 있습니다. 동기화(synchronization)를 이용해 해결 할 수 있지만, 추가적인 오버헤드 발생 가능성이 있습니다.
- 성능 저하: 객체가 처음 요청될 때까지 초기화를 연기하면, 해당 객체가 실제로 필요할 때까지 애플리케이션 성능이 저하될 수 있습니다. 초기화에 많은 시간이 걸리는 경우, 사용자가 해당 데이터나 서비스를 기다리는 시간이 늘어나게 됩니다.
- 초기화 순서 문제: 객체 간의 종속성이 있는 경우, 적절한 초기화 순서를 보장하는 것이 어려울 수 있습니다. Lazy Initialization이 이를 복잡하게 만들 수 있습니다.
- 이런 문제를 피하기 위해, 멀티스레드 환경에서는 동기화 메커니즘을 사용해야 하며, 종속성이 있는 경우는 초기화 순서를 적절히 관리해야 합니다. 또한 초기화 시간이 긴 객체의 경우, 비동기 방식으로 초기화를 고려해볼 수 있습니다.
public class LazyInitializationSingleton {
/**
* 여기서 volatile 키워드는 instance가 여러 스레드에 의해 공유 되므로,
* 한 스레드가 instance를 수정하면 다른 스레드가 이를 즉시 보도록 합니다.
*/
private static volatile LazyInitializationSingleton instance;
private LazyInitializationSingleton() {}
public static synchronized LazyInitializationSingleton getInstance() {
/*
if (instance == null) {
instance = new LazyInitializationSingleton();
}
*/
/**
* Lazy Initialization에서 동기화 메커니즘을 사용하는 일반적인 방법 중 하나는 "double-checked locking" 패턴입니다.
* 이 패턴은 인스턴스가 이미 초기화 되었는지 먼저 확인 하고(첫 번째 검사),
* 초기화되지 않았을 경우에만 동기화 블록을 실행 합니다.
* 동기화 블록 내에서는 다시 한번 인스턴스가 초기화 되었는지 확인합니다.(두 번째 검사)
*
* 그러나 double-checked locking 패턴은 복잡하고, 올바르게 사용하지 않으면 문제가 발생할 수 있습니다.
* 따라서 대부분의 경우, 초기화를 위한 동기화를 단순화하는 것이 좋습니다.
* 예를 들어, Java에서는 Bill Pugh Singleton 패턴이 이러한 목적에 적합합니다.
*/
if (instance == null) {
synchronized(LazyInitializationSingleton.class) {
if (instance == null) {
instance = new LazyInitializationSingleton();
}
}
}
return instance;
}
public String hello() {
return "hello, Lazy Initialization";
}
}
public class LazyMain {
public static void main(String[] args) {
LazyInitializationSingleton lazy = LazyInitializationSingleton.getInstance();
System.out.println(lazy.hello());
}
}
LazyMain.main(null)
hello, Lazy Initialization
- Bill Pugh Singleton
- 내부 정적 클래스를 사용해 싱글톤을 구현하는 방법.
- 내부 정적 클래스가 JVM에 로드 될 때 싱글톤 인스턴스를 생성하므로, Lazy Initialization의 장점을 가지면서도 멀티스레드 환경에 안전 함
- 단점
- 복잡도: 다른 싱글톤 패턴들에 비해 이해하고 구현하는 데에 약간 더 복잡할 수 있습니다. 이 패턴은 내부 정적 보조 클래스를 사용해 싱글톤 인스턴스를 생성하기 때문에 코드를 처음 보는 사람에게는 혼란스러울 수 있습니다.
- 리플렉션의 취약성: 리플렉션을 이용하면 private 생성자를 통해 여러개의 인스턴스를 만들 수 있습니다.
- 직렬화 역직렬화: 직렬화 후 역직렬화 하는 과정에서 새로운 인스턴스가 생길 수 있습니다. 이를 방지하기 위해서는 readResolve()메서드를 구현해야 합니다.
- 결론
- 위 단점들에도 불구하고, Bill pugh Singleton 패턴은 Lazy initialization 장점과 스레드의 안전성을 모두 충족하면서, 동기화 오버헤드 없이 싱글톤 인스턴스를 생성하는 방법으로 잘 알려져 있습니다.
- 이러한 장점이 단점을 상쇄하는 경우가 많습니다.
public class BillPughSingleton {
private BillPughSingleton() {}
private static class SingletonHelper {
private static final BillPughSingleton instance = new BillPughSingleton();
}
public static BillPughSingleton getInstance() { return SingletonHelper.instance; }
public String hello() { return "hello, Bill pugh"; }
/**
* readResolve() 메서드는 싱글톤 인스턴스를 반환하므로,
* 역직렬화 후에도 기존 싱글톤 인스턴스를 유지할 수 있습니다.
* 이 방법을 통해 싱글톤 패턴이 역직렬화 과정에서도 안전하게 유지 될 수 있습니다.
*/
protected Object readResolve() { return getInstance(); }
}
public class BillPughMain {
public static void main(String[] args) {
BillPughSingleton billPugh = BillPughSingleton.getInstance();
System.out.println(billPugh.hello());
}
}
BillPughMain.main(null);
hello, Bill pugh
- Enum Singleton
- Enum을 사용해 싱글톤을 구현하는 방법
- 자바 Enum이 쓰레드에 안전하고 한번만 로드 되는 것을 보장하기 때문에, 가장 안전하고 간단하게 싱글톤을 구현 할 수 있습니다.
- 단점
- 유연성 제한: 상속을 통한 확장이 불가능 합니다. 즉, 싱글톤이 다른 클래스를 상속해야하는 경우에는 Enum Singleton을 사용할 수 없습니다.
- 직렬화 제한: Enum은 자바에서 직렬화가 자동으로 처리되므로, 개발자가 직렬화 과정을 세밀하게 제어할 수 없습니다.
- 리플렉션 공격에 대한 방어 제한: Enum은 리플렉션을 통해 공격받을 가능성이 낮지만, 완벽하게 방어할 수는 없습니다. 자바 리플렉션 API는 Enum도 공격할 수 있습니다.
- 코드 가독성: Enum을 사용한 싱글톤 패턴은 일반적인 클래스 기반의 싱글톤 패턴보다 이해하기 어려울 수 있습니다. 따라서 코드를 읽는 사람이 이 패턴에 익숙하지 않다면 혼란을 줄 수 있습니다.
public enum EnumSingleton {
INSTANCE;
public String hello() { return "hello, Enum Singleton"; }
}
public class EnumMain {
public static void main(String[] args) {
EnumSingleton enum1 = EnumSingleton.INSTANCE;
System.out.println(enum1.hello());
}
}
EnumMain.main(null)
hello, Enum Singleton
728x90
'프로그래밍 > 코드디자인패턴' 카테고리의 다른 글
[코드디자인패턴-JAVA] Facade Pattern (0) | 2024.02.29 |
---|---|
[코드디자인패턴-JAVA] Factory Method Pattern (0) | 2024.02.29 |
[코드디자인패턴-JAVA] Observer Pattern (0) | 2024.02.29 |
[코드디자인패턴-JAVA] State Pattern (0) | 2024.02.29 |
[코드디자인패턴-JAVA] Strategy Pattern (0) | 2024.02.29 |
Comments