싱글톤 패턴 (Singleton Pattern)
한눈에 보기
인스턴스를 단 하나만 만들어 전역 접근 지점을 제공하는 생성 패턴이다. 이중 검사 잠금과 volatile, 정적 내부 클래스, enum까지 구현 방식의 진화와 안티 패턴 논쟁을 정리한다.
최종 업데이트 2026.09.17
싱글톤 패턴(Singleton Pattern)은 클래스의 인스턴스가 애플리케이션 전체에서 단 하나만 존재하도록 보장하고, 그 인스턴스에 접근하는 전역 지점을 제공하는 생성 디자인 패턴이다. 싱글톤 패턴은 설정 관리자, 커넥션 풀, 로거처럼 공유 자원을 다루는 객체에 주로 쓰인다. 구현은 간단해 보이지만 멀티스레드 안전성과 테스트 어려움이라는 함정이 있어 면접에서 가장 자주 등장하는 패턴 중 하나다.
패턴의 의도
어떤 객체는 시스템에 둘 이상 존재하면 문제가 된다. 데이터베이스 커넥션 풀이 여러 개면 연결 수 제한이 무의미해지고, 설정 객체가 여러 개면 서로 다른 설정을 읽는 코드가 생길 수 있다. 싱글톤 패턴은 두 가지 요구를 동시에 만족시킨다. 첫째, 생성자를 private으로 막아 외부에서 인스턴스를 임의로 만들 수 없게 하고, 둘째, 유일한 인스턴스를 돌려주는 정적 접근 메서드를 제공해 어디서든 같은 객체를 얻게 한다. 즉 인스턴스 개수의 통제와 전역 접근을 클래스 스스로 책임지는 구조다.
구현 방법의 진화
가장 단순한 구현은 즉시 초기화(eager initialization)다. 클래스 로딩 시점에 인스턴스를 만들어 두므로 스레드 안전이 JVM의 클래스 로딩 규칙에 의해 공짜로 보장된다. 다만 사용하지 않아도 인스턴스가 만들어진다.
public class Settings {
private static final Settings INSTANCE = new Settings();
private Settings() {} // 외부 생성 차단
public static Settings getInstance() {
return INSTANCE;
}
}
인스턴스 생성 비용이 커서 지연 초기화(lazy initialization)가 필요하다면 이야기가 복잡해진다. getInstance 안에서 null 검사 후 생성하는 순진한 방식은 멀티스레드 환경에서 두 스레드가 동시에 검사를 통과해 인스턴스가 두 개 만들어질 수 있다. 메서드 전체에 synchronized를 걸면 안전하지만 매 호출마다 락 비용이 든다. 이를 개선한 것이 이중 검사 잠금(Double-Checked Locking)이다.
public class Connection {
// volatile이 없으면 재배치 때문에 미완성 객체가 노출될 수 있다
private static volatile Connection instance;
private Connection() {}
public static Connection getInstance() {
if (instance == null) { // 1차 검사(락 없이)
synchronized (Connection.class) {
if (instance == null) { // 2차 검사(락 안에서)
instance = new Connection();
}
}
}
return instance;
}
}
volatile이 필수인 이유를 설명할 수 있어야 한다. 객체 생성은 메모리 할당, 생성자 실행, 참조 대입의 여러 단계로 이루어지는데, JVM의 명령어 재배치로 생성자 실행 전에 참조가 먼저 대입되면 다른 스레드가 초기화가 덜 된 객체를 볼 수 있다. volatile은 이 재배치를 금지하고 가시성을 보장한다.
더 우아한 대안은 정적 내부 클래스(holder) 방식이다. 내부 클래스는 처음 참조되는 순간 로딩되므로 지연 초기화와 스레드 안전을 동시에 얻고 코드도 짧다.
public class Logger {
private Logger() {}
private static class Holder {
private static final Logger INSTANCE = new Logger();
}
public static Logger getInstance() {
return Holder.INSTANCE;
}
}
마지막으로 enum 싱글톤이 있다. 원소가 하나인 enum은 JVM이 인스턴스 유일성을 보장하고, 리플렉션으로 생성자를 호출하는 공격과 직렬화 후 역직렬화로 새 인스턴스가 생기는 문제까지 언어 차원에서 막아 준다. 이펙티브 자바(Effective Java)가 권장하는 방식으로 널리 알려져 있다.
public enum AppConfig {
INSTANCE;
public String get(String key) {
return "value";
}
}
장단점과 사용 시점
장점은 유일성 보장과 전역 접근, 그리고 지연 초기화 시 자원 절약이다. 상태를 공유해야 하는 무거운 객체(커넥션 풀, 캐시, 스레드 풀)를 한 번만 만들어 재사용할 수 있다.
한 가지 더 알아 둘 함정은 유일성이 "클래스 로더 하나당"이라는 점이다. 서로 다른 클래스 로더가 같은 클래스를 각각 로딩하면 싱글톤이 로더마다 하나씩 생길 수 있고, 직렬화 가능한 싱글톤은 readResolve 메서드를 정의하지 않으면 역직렬화 때 새 인스턴스가 만들어진다. 리플렉션으로 private 생성자를 강제로 호출하는 것도 일반 구현으로는 막기 어렵다. 이런 경계 사례까지 언어 차원에서 방어해 주는 것이 enum 방식의 강점이다.
그러나 단점이 만만치 않아 안티 패턴 논쟁의 단골이다. 정적 메서드로 얻는 싱글톤은 사실상 전역 상태여서 어떤 코드가 어디서 그것을 수정했는지 추적하기 어렵다. 의존성이 코드에 숨어 있어(메서드 내부에서 getInstance 호출) 테스트 시 가짜 객체로 교체하기 힘들고, 테스트 간 상태가 공유되어 테스트 격리도 깨진다. 단일 책임 원칙 관점에서도 "자신의 비즈니스"와 "자기 생명주기 관리"라는 두 책임을 진다. 이런 이유로 현대 실무에서는 클래스가 스스로 싱글톤을 구현하기보다, Spring 같은 컨테이너에 빈(bean)을 등록해 컨테이너가 하나만 만들어 주입하게 하는 방식을 선호한다. Spring의 싱글톤은 언어 차원의 강제가 아니라 컨테이너가 관리하는 스코프라는 점도 구분해서 알아 두자.
구현 방식 비교
| 구현 방식 | 지연 초기화 | 스레드 안전 | 비고 |
|---|---|---|---|
| 즉시 초기화 | 안 된다 | 보장 | 가장 단순, 미사용 시 낭비 |
| synchronized 메서드 | 된다 | 보장 | 매 호출 락 비용 |
| 이중 검사 잠금 | 된다 | volatile 필수 | 구현 실수 여지가 크다 |
| 정적 내부 클래스 | 된다 | 보장 | 간결, 실무 권장 |
| enum | 클래스 로딩 시 | 보장 | 리플렉션, 직렬화 공격까지 방어 |
면접에서 이렇게 물어본다
- 싱글톤 패턴을 멀티스레드 환경에서 안전하게 구현하는 방법을 두 가지 이상 코드 수준으로 설명해 보라.
- 이중 검사 잠금에서
volatile키워드가 반드시 필요한 이유는 무엇인가. - 싱글톤 패턴이 안티 패턴이라고 비판받는 이유는 무엇이며, 테스트 관점에서 어떤 문제가 있는가.
- enum으로 싱글톤을 구현하면 어떤 공격(리플렉션, 직렬화)을 막을 수 있는가.
- Spring의 싱글톤 빈과 디자인 패턴의 싱글톤은 어떻게 다른가.
디자인 패턴에서 같이 읽기
- 어댑터 패턴 (Adapter Pattern)호환되지 않는 인터페이스를 클라이언트가 기대하는 형태로 변환해 함께 동작하게 만드는 구조 패턴이다. 타깃, 어댑티, 어댑터, 클라이언트의 협력 구조와 장단점, 유사 패턴과의 차이를 정리한다.
- 어댑터 패턴 Java 구현 (Adapter Pattern in Java)InputStreamReader, Arrays.asList 등 JDK 속 어댑터 사례를 살펴보고, 클래스 어댑터와 객체 어댑터 두 구현 변형의 차이와 Java에서 객체 어댑터가 선호되는 이유를 정리한다.
- 컴포지트 패턴 (Composite Pattern)개별 객체와 객체 묶음을 같은 인터페이스로 다루어 트리 구조를 표현하는 구조 패턴이다. 파일과 폴더 예시 코드로 재귀 구조를 설명하고 투명성과 안전성의 트레이드오프를 다룬다.
- 디자인 패턴 개요 (GoF Design Patterns)반복되는 설계 문제의 검증된 해결 구조인 디자인 패턴을 GoF의 생성, 구조, 행위 세 분류로 조망한다. 패턴 선택 기준과 오남용 경계, 면접 대비 학습 전략까지 정리한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기