Mobile & App Stack/Java Software Architecture & Patterns

자바 싱글톤 패턴(Singleton) 완벽 정리: 5가지 구현법과 스레드 안전성 비교

임베디드 친구 2024. 12. 19. 13:04
반응형

애플리케이션을 개발하다 보면 시스템 설정, 로그 기록기, 데이터베이스 커넥션 풀처럼 전체 시스템에서 단 하나만 존재해야 하는 인스턴스가 필요한 경우가 있습니다. 이때 사용하는 것이 바로 싱글톤 패턴(Singleton Pattern)입니다.

오늘은 싱글톤 패턴의 개념부터, 멀티스레드 환경에서도 안전하게 사용할 수 있는 5가지 구현 기법을 깊이 있게 살펴보겠습니다.

Generated by Gemini AI.


1. 싱글톤 패턴(Singleton Pattern)이란?

싱글톤 패턴은 특정 클래스의 인스턴스를 오직 하나만 생성하도록 보장하고, 어디서든 이 인스턴스에 접근할 수 있는 전역 접근점(Global Access Point)을 제공하는 디자인 패턴입니다.

왜 사용하는가?

  • 메모리 절약: 인스턴스를 매번 생성하지 않고 재사용하므로 고정된 메모리 영역을 효율적으로 사용합니다.
  • 데이터 공유 용이: 전역 인스턴스이므로 다른 클래스 간의 데이터 공유가 쉽습니다.
  • 리소스 관리: 하드웨어 인터페이스나 로그 핸들러 등 공용 리소스에 대한 접근을 제어하기에 최적입니다.

2. 자바 싱글톤 구현 방법 5가지

자바에서 싱글톤을 구현할 때는 '언제 생성할 것인가(Initialization)'와 '멀티스레드에서 안전한가(Thread-Safe)'가 핵심입니다.

2.1 이른 초기화 (Eager Initialization)

클래스 로딩 시점에 인스턴스를 바로 생성하는 방식입니다.

Java
 
public class Singleton {
    // static final로 thread-safe 보장
    private static final Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton getInstance() {
        return instance;
    }
}
  • 특징: 구현이 가장 단순하며, 클래스 로더에 의해 스레드 안전성이 보장됩니다.
  • 주의: 인스턴스를 사용하지 않더라도 메모리를 점유하므로, 리소스가 큰 객체일 경우 효율성이 떨어집니다.

2.2 지연 초기화 (Lazy Initialization)

인스턴스가 실제 필요한 시점에 생성하는 방식입니다.

Java
 
public class Singleton {
    private static Singleton instance;

    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 특징: 메모리 낭비를 막을 수 있습니다.
  • 위험: 멀티스레드 환경에서 동시에 getInstance()를 호출하면 인스턴스가 여러 개 생길 수 있는 Thread-safe 하지 않은 방식입니다.

2.3 스레드 안전한 지연 초기화 (Thread-Safe Lazy)

synchronized 키워드를 사용하여 동시성 문제를 해결합니다.

Java
 
public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}
  • 단점: 메서드 전체에 락(Lock)이 걸리므로 성능 저하가 심각할 수 있습니다. 권장되지 않는 방식입니다.

2.4 더블 체크 락킹 (Double-Checked Locking, DCL)

인스턴스가 생성되지 않았을 때만 synchronized 블록을 타도록 최적화한 방식입니다.

Java
 
public class Singleton {
    // volatile을 통해 메인 메모리에서의 가시성 확보
    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;
    }
}
  • 특징: 성능과 스레드 안전성을 모두 잡은 방식입니다. 다만 volatile 키워드에 대한 이해가 필요하며 코드가 다소 복잡합니다.

2.5 Bill Pugh Singleton (Static Inner Class) - 가장 추천

자바의 클래스 로딩 시점을 이용한 방식으로, 현대 자바 환경에서 가장 권장되는 기법입니다.

Java
 
public class Singleton {
    private Singleton() { }

    // 내부 클래스는 getInstance() 호출 시점에만 로드됨
    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}
  • 장점: synchronized 없이도 스레드 안전하며, 지연 초기화(Lazy Loading)가 가능하여 성능과 메모리 효율이 모두 뛰어납니다.

3. 요약 및 비교표

구현 방식 스레드 안전성 지연 초기화 장점 단점
이른 초기화 O X 단순함, 안전함 미사용 시 리소스 낭비
지연 초기화 X O 메모리 효율적 멀티스레드 위험
DCL 방식 O O 성능 최적화 코드가 다소 복잡함
Bill Pugh O O 최고의 효율성 거의 없음

4. 실무 활용 예시: Logger 클래스

실제 프로젝트에서 로그를 남길 때 싱글톤이 어떻게 쓰이는지 간단한 예제로 확인해 보세요.

Java
 
public class Logger {
    private Logger() {}

    private static class Holder {
        private static final Logger INSTANCE = new Logger();
    }

    public static Logger getInstance() {
        return Holder.INSTANCE;
    }

    public void log(String msg) {
        System.out.println("[시스템 로그] " + msg);
    }
}

// 사용 예시
Logger.getInstance().log("싱글톤 패턴 적용 완료!");

결론

싱글톤 패턴은 단순해 보이지만 멀티스레드 환경에서의 동시성 이슈를 깊게 다루는 패턴입니다. 성능과 안전성을 모두 고려한다면 Bill Pugh Singleton 방식을 적극 활용해 보시기 바랍니다.

이 포스팅이 여러분의 객체지향 설계에 도움이 되었기를 바랍니다!

반응형