Conceptly
← 전체 목록
1️⃣

Singleton

생성프로세스 전체에서 인스턴스를 하나만 유지하는 생성 패턴

Singleton은 클래스의 인스턴스가 프로세스 안에서 정확히 하나만 존재하도록 보장하고, 그 인스턴스에 접근할 전역적인 진입점을 만드는 생성 패턴입니다. 생성자를 숨기고 정적 메서드를 통해서만 객체를 내보내기 때문에, 누가 언제 호출하든 같은 인스턴스를 받게 됩니다.

아키텍처 다이어그램

🔍 구조 다이어그램

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

앱이 커지면 여러 모듈이 같은 자원을 써야 하는 상황이 생깁니다. 데이터베이스 커넥션 풀, 로거, 설정 객체 같은 것들입니다. 이때 각 모듈이 자기 것을 따로 만들면 커넥션 수가 통제 불능으로 불어나거나, 설정 값이 모듈마다 달라지는 문제가 터집니다. 전역 변수를 쓰면 접근은 쉬워지지만 초기화 시점이 불분명하고, 어디서든 값을 덮어쓸 수 있어 추적이 어렵습니다. '하나만 있어야 하는 객체'를 코드 수준에서 강제할 방법이 없으면, 규약만으로 막아야 하고 규약은 사람이 잊으면 끝입니다.

왜 이런 방식이 등장했나요?

초기 객체지향 설계에서는 전역 자원이 필요할 때 전역 변수나 정적 상태에 직접 기대는 경우가 많았습니다. 앱이 작을 때는 버틸 수 있었지만, 모듈이 늘어나자 누가 언제 객체를 만들고 초기화하는지 추적하기 어려워졌고, 같은 역할의 객체가 중복 생성되며 상태가 어긋나는 문제가 반복됐습니다. '프로세스 안에 하나만 있어야 하는 자원'을 규약이 아니라 구조로 강제해야 한다는 압력 속에서, 생성자를 숨기고 접근 지점을 하나로 모으는 Singleton이 정리됐습니다.

지금은 어떻게 읽어야 하나요?

지금 Singleton은 '전역 자원이 왜 생명주기 관리 대상이 되는가'를 이해하는 기본 패턴으로는 여전히 중요하지만, 애플리케이션 설계에서 직접 구현을 남발하는 권장 해법으로 읽히지는 않습니다. 현대 프레임워크는 DI 컨테이너와 scope 설정으로 같은 효과를 더 명시적으로 관리하고, 테스트에서도 교체가 쉽도록 만듭니다. 그래서 오늘날에는 Singleton을 '직접 써야 할 정답'보다, 왜 인스턴스 생명주기와 의존성 노출이 중요한지 보여 주는 출발점으로 읽는 편이 정확합니다.

내부적으로 어떻게 동작하나요?

Singleton의 메커니즘은 세 단계로 나뉩니다. 첫째, 생성자를 private으로 막습니다. 외부에서 new를 쓸 수 없으니, 아무도 두 번째 인스턴스를 만들 수 없습니다. 둘째, 클래스 안에 자기 자신의 인스턴스를 담을 정적 필드를 둡니다. 처음에는 비어 있습니다. 셋째, getInstance() 같은 정적 메서드를 통해 접근합니다. 이 메서드는 정적 필드가 비어 있으면 인스턴스를 하나 만들어 채우고, 이미 있으면 그대로 돌려줍니다. 결과적으로 몇 번을 호출하든 반환되는 객체는 항상 같습니다. 멀티스레드 환경에서는 두 스레드가 동시에 getInstance()를 호출할 때 인스턴스가 두 개 생길 수 있어, synchronized 블록이나 volatile 필드 같은 동기화 장치를 추가해야 합니다.

경계와 구분

Singleton과 전역 변수는 둘 다 '어디서든 접근할 수 있는 하나의 값'을 만든다는 점에서 비슷합니다. 하지만 전역 변수는 초기화 시점과 생명주기를 언어 런타임에 맡기고 아무나 덮어쓸 수 있는 반면, Singleton은 생성 시점을 코드가 통제하고 외부에서 새 인스턴스를 만들지 못하게 강제합니다. Singleton과 DI 컨테이너의 singleton scope는 '인스턴스 하나'라는 결과가 같지만, 누가 생명주기를 관리하느냐가 다릅니다. Singleton 패턴은 클래스가 스스로 '나는 하나뿐'이라고 강제하고, DI 컨테이너는 외부 설정으로 scope를 지정해 같은 효과를 냅니다. 테스트에서 인스턴스를 교체해야 하거나 의존성을 명시적으로 드러내야 하는 상황이라면 DI 쪽이 더 유연합니다. 반대로 프레임워크 없이 가볍게 전역 자원을 하나로 묶어야 할 때는 Singleton이 가장 직관적입니다.

트레이드오프

이 패턴의 이득은 분명합니다. 하나만 있어야 하는 자원을 구조로 강제해 중복 생성과 상태 불일치를 줄이고, 호출자 입장에서는 접근 경로를 단순하게 만들 수 있습니다. 비용도 큽니다. 전역 접근점이 숨은 의존을 만들고 테스트 격리가 어려워지며, 초기화 순서와 상태 오염 문제가 시스템 전체로 번집니다. 판단 기준은 '정말 하나여야 하는 자원인가, 아니면 그냥 공유하면 편한가'입니다. 전자라면 Singleton이나 컨테이너의 singleton scope가 후보가 되지만, 후자라면 명시적 주입이 더 안전합니다.

언제 쓰나요?

Singleton은 '프로세스 안에서 정확히 하나만 있어야 하는' 자원을 다룰 때 등장합니다. 로거, 설정 매니저, 커넥션 풀, 스레드 풀 같은 인프라 객체가 대표적입니다. 이런 객체를 여러 개 만들면 자원이 낭비되거나 상태가 어긋나기 때문에, 하나로 고정하고 어디서든 접근하게 해야 합니다. 다만 Singleton을 남발하면 클래스 간 숨은 의존이 늘어나고, 테스트에서 상태를 초기화하기 어려워집니다. 비즈니스 로직 객체까지 Singleton으로 만들면 코드가 전역 상태에 점점 더 묶이게 됩니다. 적용 전에 '이 객체가 진짜 하나여야 하는가, 아니면 같은 인스턴스를 공유하는 편이 편한 것뿐인가'를 따져 보는 게 좋습니다. 후자라면 DI 컨테이너의 생명주기 관리가 더 적합할 수 있습니다.

설정 관리로깅커넥션 풀캐시