싱글톤 패턴이란
애플리케이션 전역에서 단 하나의 인스턴스만 생성하고 공유(전역 접근) 하기 위한 디자인 패턴임.
싱글톤 패턴은 “전역에서 하나로 관리해야 하는 객체”를 깔끔하게 구현하고, 자원 낭비를 막으며 일관된 상태를 보장할 수 있다.
- 유일성 보장과 공유자원에 적합한 전역 접근
private 생성자로 외부 인스턴스 생성을 막고, static 필드에 자신을 저장한 후,
public 메서드 getInstance()와 같은 메서드로 언제 어디서든 동일한 인스턴스를 반환.
- 스레드 안정성 고려
멀티스레드 환경에선 여러 쓰레드가 동시에 getInstance() 메서드를 호출해 인스턴스 중복생성을 막도록,
synchronized, 이른 초기화(Eager Init) 또는 Static Inner Class(Holder) 기법을 사용해 안전하게 구현함.
주로
- 애플리케이션 설정(Config) 객체
- 로깅(Log) 객체
- DB 커넥션 풀, 쓰레드풀 등 한곳에 집중 관리해야 하는 공유 자원
에 사용함
예를 들어 다음 코드처럼
package single;
public class SingletonNotThreadSafe {
private static SingletonNotThreadSafe instance; // 초기값은 null
private int count = 0;
private SingletonNotThreadSafe() {
// private 생성자
}
public static SingletonNotThreadSafe getInstance() {
if (instance == null) {
// 여러 쓰레드가 이 부분을 동시에 진입하면 instance 가 두 번 이상 생성될 수 있음
instance = new SingletonNotThreadSafe();
}
return instance;
}
}
인스턴스 생성을 담당하는 클래스
실제 싱글톤 인스턴스를 호출하는 클래스
package single;
public class SingletonEx {
public static void main(String[] args) {
SingletonNotThreadSafe notSafeSingleton1 = SingletonNotThreadSafe.getInstance();
SingletonNotThreadSafe notSafeSingleton2 = SingletonNotThreadSafe.getInstance();
System.out.println(notSafeSingleton1);
System.out.println(notSafeSingleton2);
}
}
결과를 보면 생성된 인스턴스는 같은 객체를 참조한다.
single.SingletonNotThreadSafe@119d7047
single.SingletonNotThreadSafe@119d7047
싱글톤 패턴을 사용하면 다음의 장점을 얻을 수 있다.
인스턴스 제어
- 애플리케이션에서 단 하나의 객체만 생성되므로 메모리 낭비를 방지할 수 있다.
전역 접근 지점 제공
- getInstance()를 통해 어디서나 동일한 객체에 접근할 수 있어, 설정(config), 로거, 커넥션 풀 같은 공용 자원을 관리하기에 편리하다.
초기화 시점 제어
- 이른 초기화(Eager)나 지연 초기화(Lazy) 방식을 선택해, 애플리케이션 구동 성능이나 자원 사용 타이밍을 조절할 수 있다.
상태 일관성 보장
- 단일 인스턴스이므로 객체 내부 상태가 분산되지 않고 한곳에 집중되어, 관리·디버깅이 쉬워진다.
반면 단점으로는
전역 상태(Global State)
- 전역 객체를 쓰면 숨은 의존성이 생기고, 모듈 간 결합도가 높아져 테스트나 리팩토링이 어려워진다.
테스트·모킹 어려움
- JVM 내에 인스턴스가 하나만 존재하므로, 단위 테스트 시마다 상태를 초기화하기 불편하거나 별도 후킹이 필요하다.
확장성·유연성 저하
- 상속이나 다형성 활용이 어렵고, DIP(의존성 역전 원칙) 위반 소지가 있어 구조 변경 시 제약이 생긴다.
직렬화·리플렉션 우회 이슈
- Serializable 직렬화 과정에서 새로운 인스턴스가 만들어지거나, 리플렉션을 통해 private 생성자를 우회해 복수 생성될 수 있다.
멀티스레드 안전성 관리 부담
- 멀티스레드 환경에서 인스턴스 생성·초기화 시 동기화(synchronized)나 Holder 기법 등을 반드시 적용해야 한다.
쓰레드 안정성을 고려한 싱글톤 패턴
위에서 예시로 본 코드는 쓰레드안정성을 고려하지 않은 단순한 싱글톤 예제였다.
쓰레드 안정성을 고려하지 않는다면 다음 코드에서
package single;
public class SingletonNotThreadSafe {
private static SingletonNotThreadSafe instance; // 초기값은 null
private int count = 0;
private SingletonNotThreadSafe() {
// private 생성자
}
public static SingletonNotThreadSafe getInstance() {
if (instance == null) {
// 여러 쓰레드가 이 부분을 동시에 진입하면 instance 가 두 번 이상 생성될 수 있음
instance = new SingletonNotThreadSafe();
}
return instance;
}
}
여러 개의 쓰레드에서 동시에 getInstance()를 호출하면,
package single;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;
public class SingletonEx {
public static void main(String[] args) {
final int THREADS = 500_000;
// 병렬로 getInstance()만 호출 → 최초 생성이 동시다발적으로 일어나게 함
Set<SingletonNotThreadSafe> instances =
Collections.newSetFromMap(new ConcurrentHashMap<>());
IntStream.range(0, THREADS)
.parallel()
.forEach(i ->
instances.add(SingletonNotThreadSafe.getInstance())
);
System.out.println("Expected unique instances: 1");
System.out.println("Actual unique instances: " + instances.size());
}
}
다음과 같이 하나 이상의 인스턴스가 생성된 결과가 나온다.

이처럼 싱글톤 패턴이 깨지게 되는데 이를 방지하기 위해
대표적으로 다음 네 가지 방식으로 싱글톤을 구현해 단일 인스턴스를 보장할 수 있다.
기존싱글톤
package single;
public class SingletonNotThreadSafe {
private static SingletonNotThreadSafe instance; // 초기값은 null
private SingletonNotThreadSafe() {
}
public static SingletonNotThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonNotThreadSafe();
}
return instance;
}
}
synchronized 키워드(Lazy + Synchronized Method) 방식
getInstance() 전역에 synchronized를 걸어두었기 때문에, 여러 스레드 동시 진입 시에도 오직 한 번만 생성됨.
package single;
public class SingletonSync {
private static SingletonSync instance;
private SingletonSync() {}
public static synchronized SingletonSync getInstance() {
if (instance == null) {
instance = new SingletonSync();
}
return instance;
}
}
Eager Initialization 방식
클래스 로딩 시점에 인스턴스를 딱 한 번 생성하므로, 병렬 호출로도 인스턴스 추가 생성이 절대 일어나지 않음.
package single;
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {
}
public static SingletonEager getInstance() {
return INSTANCE;
}
}
Static Inner Class Holder (Lazy + No Sync Overhead) 방식
Holder 클래스의 초기화 시점(최초 getInstance() 호출)도 JVM 스펙으로 “한 번만” 보장되므로, 병렬 호출로도 인스턴스가 하나임.
package single;
public class SingletonHolder {
private SingletonHolder() {}
private static class Holder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.INSTANCE;
}
}
enum 싱글톤 방식
자바 언어 차원에서 “한 개의 enum 상수”만 생성되도록 보장하기 때문에, 어떠한 병렬 호출에서도 인스턴스는 1개임.
package single;
public enum SingletonEnum {
INSTANCE;
// 메서드, 필드 추가 가능
}
결론 :
싱글톤 패턴은 애플리케이션 전역에서 단 하나의 인스턴스만 생성하고 공유(전역 접근) 하기 위한 디자인 패턴이다.
싱글톤 패턴을 사용하면 메모리, 데이터, 속도 측면에서 이점을 얻지만, 전역 객체로 관리가 어려울 수 있다.
멀티 쓰레드 환경에서 쓰레드 안정성을 고려해서 설계하지 않으면 여러 개의 인스턴스를 생성하게 될 수 있으니 잘 설계하자.
'정리' 카테고리의 다른 글
| 스프링의 @Transactional (0) | 2025.05.02 |
|---|---|
| 스프링 Bean의 scope (0) | 2025.04.30 |
| 자바 동시성이슈 (0) | 2025.04.28 |
| 동기(Synchronous)와 비동기(Asynchronous), blocking과 nonBlocking (0) | 2025.04.27 |
| RDBMS vs NoSQL (0) | 2025.04.26 |