싱글톤 패턴은 클래스가 단일 인스턴스만 생성되도록 제한하는 설계 패턴이다. 이 패턴은 다음과 같은 특성을 가진다:
- 유일한 인스턴스만 존재하도록 보장
- 해당 인스턴스에 접근할 수 있는 메서드 제공
- 인스턴스 생성 과정을 제어(예: 생성자 비공개화)
실제 사례로는 사무실 내 모든 장치가 공유하는 와이파이 네트워크를 들 수 있다. 각 장치마다 별도의 네트워크를 구성하는 대신 하나의 네트워크를 공유함으로써 자원을 효율적으로 사용하는 것이다.
스프링 프레임워크에서는 IOC 컨테이너가 싱글톤 패턴의 전형적인 구현 예시이다. @Controller, @Service, @Component, @Configuration 어노테이션으로 등록된 빈들은 IOC 컨테이너에 의해 관리되며, @Autowired나 생성자 주입 등을 통해 쉽게 참조할 수 있다.
하지만 IOC 외에도 일반적으로 두 가지 방식으로 싱글톤을 구현한다:
- 1️⃣ 이중 검사 잠금(DCL - Double Check Locking)
- 2️⃣ CAS 연산(Compare-And-Swap)
두 방식의 특징을 살펴보면:
1️⃣ 완전한 싱글톤을 보장하지만 멀티스레딩 환경에서 성능 저하가 발생할 수 있다. 2️⃣ 진정한 의미의 싱글톤은 아니지만 잠금 오버헤드를 피할 수 있어 고성능 환경에 적합하다.
IOC 기반 싱글톤이 존재함에도 불구하고 직접 구현이 필요한 경우는 다음과 같다. 사용 빈도가 낮은 유틸리티 클래스의 경우, IOC에 등록하면 사용되지 않아도 GC 대상이 되지 않아 메모리 낭비가 발생할 수 있다. 또는 오래된 시스템에서는 XML 기반 설정만 지원하는 경우도 있어 직접 구현이 더 유연할 수 있다.
/**
* 낮은 사용 빈도의 유틸리티 클래스에 적합
*/
public class UniqueInstanceManager {
private static volatile UniqueInstanceManager manager;
private UniqueInstanceManager() {
// 초기화 처리 로직
}
public static UniqueInstanceManager getManager() {
if (manager == null) {
synchronized (UniqueInstanceManager.class) {
if (manager == null) {
manager = new UniqueInstanceManager();
}
}
}
return manager;
}
}
/**
* 고성능 동시성 환경 (가능하다면 IOC 싱글톤 사용 권장)
*/
public class ConcurrentSingleton {
private static final AtomicReference<ConcurrentSingleton> holder = new AtomicReference<>();
private ConcurrentSingleton() {
// 초기화 처리 로직
}
public static ConcurrentSingleton getHolder() {
while (true) {
ConcurrentSingleton existing = holder.get();
if (existing != null) {
return existing;
}
existing = new ConcurrentSingleton();
if (holder.compareAndSet(null, existing)) {
return existing;
}
}
}
}