728x90
반응형
✅ 싱글톤(Singleton) 패턴이란?
어떤 객체는 시스템 전역에서 오직 하나만 존재해야 할 때가 있습니다. 예를 들어:
- 설정 정보 관리 객체
- 데이터베이스 커넥션 풀
- 로깅 시스템
이러한 요구는 객체의 생명주기를 명확히 통제하고, 동일한 자원 접근을 일관성 있게 유지해야 하는 필요에서 출발합니다. 이를 실현하는 대표적인 디자인 패턴이 싱글톤 패턴입니다.
✅ 싱글톤의 정의
- 오직 하나의 인스턴스만 생성
- 전역(Global)하게 접근 가능
- 객체 생성과 접근 과정 모두에서 스레드 안전(Thread-safe) 확보가 중요
✅ 싱글톤 구현 방식 비교
방식 | 특징 | 장점 | 단점 |
---|---|---|---|
Lazy Initialization (기본) | 필요 시 생성 | 메모리 절약 | 멀티 스레드 환경에서 다중 생성 위험 |
Synchronized Method | 메서드에 synchronized | 스레드 안전 | 호출마다 락, 성능 저하 |
Eager Initialization | 클래스 로딩 시 인스턴스 생성 | 단순, 빠름 | 항상 생성 → 리소스 낭비 가능성 |
Double-Checked Locking | 두 번 null 검사 + synchronized | 성능 + 스레드 안전 | volatile 필수 |
정적 내부 클래스 (Lazy Holder) | 내부 클래스 로딩 시 생성 | JVM 보장으로 완전한 스레드 안전, 성능 최적화 | 자바 전용 기법 |
✅ 대표 코드 예시
1. Lazy Initialization (비추천)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
➡ 문제점: 멀티 스레드 환경에서 인스턴스가 중복 생성될 수 있음
2. Synchronized Method (스레드 안전, 하지만 성능 저하)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. Eager Initialization (자주 쓰임)
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
4. Double-Checked Locking (성능과 안정성의 균형)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
5. 정적 내부 클래스 방식 (최고 권장 방식)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
➡ 클래스 초기화는 JVM이 한 번만, thread-safe하게 수행하므로 별도 synchronized 불필요
➡ 클래스 또는 인터페이스는 해당 타입이 처음으로 액세스될 때, 딱 한 번만 초기화 됨
➡ 그리고 이 초기화는 JVM 내부에서 동기화(synchronized) 되어 수행됨(= 소스 영역이 아님)
✅ 실무 구현 시 고려사항
1. 스레드 안전성
- 멀티 스레드 환경이라면 반드시 synchronized, volatile, 또는 JVM 초기화 로직 활용 필요
- 정적 내부 클래스 방식 또는 Double-Checked Locking 방식이 권장됨
2. 리플렉션 공격 방지
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
if (instance != null) {
throw new RuntimeException("이미 존재하는 인스턴스입니다!");
}
}
public static Singleton getInstance() {
return instance;
}
}
➡ 하지만 리플렉션은 여전히 강제로 접근 가능하므로 보안 목적의 완전 방지는 어려움
3. 직렬화 대응
public class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
protected Object readResolve() { // 역직렬화 시 기존 객체 반환
return instance;
}
}
➡ readResolve()를 오버라이드하지 않으면 deserialize() 시 새로운 인스턴스가 생길 수 있음
✅ 요약
- 싱글톤은 인스턴스를 하나로 제한하고 전역 접근이 필요한 객체에 적합
- 정적 내부 클래스 방식이 자바에서는 가장 안전하고 성능도 뛰어난 방식
- 스레드 안정성과 직렬화, 리플렉션 같은 보안 요소도 함께 고려해야 실무에 적합한 설계가 가능
728x90
반응형