애플리케이션을 개발하다 보면 시스템 설정, 로그 기록기, 데이터베이스 커넥션 풀처럼 전체 시스템에서 단 하나만 존재해야 하는 인스턴스가 필요한 경우가 있습니다. 이때 사용하는 것이 바로 싱글톤 패턴(Singleton Pattern)입니다.
오늘은 싱글톤 패턴의 개념부터, 멀티스레드 환경에서도 안전하게 사용할 수 있는 5가지 구현 기법을 깊이 있게 살펴보겠습니다.

1. 싱글톤 패턴(Singleton Pattern)이란?
싱글톤 패턴은 특정 클래스의 인스턴스를 오직 하나만 생성하도록 보장하고, 어디서든 이 인스턴스에 접근할 수 있는 전역 접근점(Global Access Point)을 제공하는 디자인 패턴입니다.
왜 사용하는가?
- 메모리 절약: 인스턴스를 매번 생성하지 않고 재사용하므로 고정된 메모리 영역을 효율적으로 사용합니다.
- 데이터 공유 용이: 전역 인스턴스이므로 다른 클래스 간의 데이터 공유가 쉽습니다.
- 리소스 관리: 하드웨어 인터페이스나 로그 핸들러 등 공용 리소스에 대한 접근을 제어하기에 최적입니다.
2. 자바 싱글톤 구현 방법 5가지
자바에서 싱글톤을 구현할 때는 '언제 생성할 것인가(Initialization)'와 '멀티스레드에서 안전한가(Thread-Safe)'가 핵심입니다.
2.1 이른 초기화 (Eager Initialization)
클래스 로딩 시점에 인스턴스를 바로 생성하는 방식입니다.
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)
인스턴스가 실제 필요한 시점에 생성하는 방식입니다.
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 키워드를 사용하여 동시성 문제를 해결합니다.
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
- 단점: 메서드 전체에 락(Lock)이 걸리므로 성능 저하가 심각할 수 있습니다. 권장되지 않는 방식입니다.
2.4 더블 체크 락킹 (Double-Checked Locking, DCL)
인스턴스가 생성되지 않았을 때만 synchronized 블록을 타도록 최적화한 방식입니다.
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) - 가장 추천
자바의 클래스 로딩 시점을 이용한 방식으로, 현대 자바 환경에서 가장 권장되는 기법입니다.
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 클래스
실제 프로젝트에서 로그를 남길 때 싱글톤이 어떻게 쓰이는지 간단한 예제로 확인해 보세요.
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 방식을 적극 활용해 보시기 바랍니다.
이 포스팅이 여러분의 객체지향 설계에 도움이 되었기를 바랍니다!
'Mobile & App Stack > Java Software Architecture & Patterns' 카테고리의 다른 글
| 프로토타입 패턴(Prototype Pattern) 완벽 정리: 깊은 복사와 얕은 복사 차이점 (0) | 2024.12.23 |
|---|---|
| 빌더 패턴(Builder Pattern) 완벽 정리: 생성자 대신 사용하는 이유와 구현법 (0) | 2024.12.22 |
| 추상 팩토리 패턴(Abstract Factory) 핵심 정리: 팩토리 메서드와 차이점은? (0) | 2024.12.21 |
| 팩토리 메서드 패턴(Factory Method Pattern) 완벽 정리: 왜 사용할까? (Java 예제) (0) | 2024.12.20 |
| 소프트웨어 설계의 정석: 디자인 패턴 종류 총정리 및 SOLID 원칙 가이드 (0) | 2024.12.18 |