JAVA로 구현하는 알고리즘과 디자인 패턴 공부: 싱글톤 패턴 (Singleton)
1. 싱글톤 패턴이란? 🤔
개념: 클래스의 인스턴스(객체)를 단 하나만 생성하도록 보장하고, 그 인스턴스에 대한 전역적인 접근점(Global Access Point)을 제공하는 패턴입니다. 시스템 전체에서 유일해야 하는 객체를 관리할 때 사용됩니다.
마치 한 나라에 대통령이 단 한 명만 존재하는 것과 같습니다. 누구든지 '대통령'을 부르면 항상 동일한 한 사람을 가리키게 되죠.
2. 왜 사용할까요?
싱글톤 패턴은 주로 다음과 같은 상황에서 유용합니다.
- 자원 공유: 데이터베이스 연결(Connection Pool), 스레드 풀(Thread Pool)처럼 여러 곳에서 공유해서 사용해야 하는 무거운 객체를 하나만 만들어두고 재사용하여 시스템 부하를 줄일 수 있습니다.
- 설정 관리: 시스템 전체에서 사용되는 설정 정보(Configuration)를 담는 객체를 하나로 고정하여 일관성을 유지합니다.
- 로깅: 로그를 기록하는 Logger 객체는 여러 곳에서 사용되더라도 하나의 인스턴스를 통해 로그를 남기는 것이 효율적입니다.
3. Java로 구현하는 방법
싱글톤을 구현하는 핵심은 다음과 같습니다.
- 외부에서 new 키워드로 객체를 생성하지 못하도록 생성자를 private으로 만든다.
- 클래스 내부에 자기 자신 타입의 유일한 static 인스턴스를 만든다.
- 외부에서 이 유일한 인스턴스에 접근할 수 있도록 **static 메서드(getInstance())**를 제공한다.
가. 이른 초기화 (Eager Initialization)
클래스가 로드되는 시점에 인스턴스를 미리 생성해두는 가장 간단하고 안전한 방법입니다.
- 장점: 멀티스레드 환경에서도 인스턴스가 여러 개 생성될 걱정이 없어 **스레드에 안전(Thread-Safe)**합니다.
- 단점: 인스턴스를 사용하지 않더라도 클래스가 로딩될 때 무조건 생성되므로, 메모리를 낭비할 수 있습니다.
public class SettingsManager {
// 1. 클래스 로딩 시점에 유일한 인스턴스를 미리 생성 (Eager)
private static final SettingsManager INSTANCE = new SettingsManager();
// 2. 생성자를 private으로 선언하여 외부에서 new로 생성하는 것을 막는다.
private SettingsManager() {
// 설정 파일 로딩 등 초기화 로직
System.out.println("설정 관리자 인스턴스가 생성되었습니다.");
}
// 3. 외부에서 유일한 인스턴스에 접근할 수 있는 static 메서드를 제공한다.
public static SettingsManager getInstance() {
return INSTANCE;
}
// 기타 설정 관련 메서드
public void getSetting(String key) {
System.out.println(key + " 설정 값을 가져옵니다.");
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
SettingsManager sm1 = SettingsManager.getInstance();
SettingsManager sm2 = SettingsManager.getInstance();
// sm1과 sm2는 동일한 인스턴스를 가리킨다.
System.out.println("sm1과 sm2는 같은 객체인가? " + (sm1 == sm2)); // 출력: true
sm1.getSetting("DB_URL");
sm2.getSetting("ADMIN_ID");
}
}
나. 지연 초기화 (Lazy Initialization) + Double-Checked Locking
getInstance() 메서드가 처음 호출될 때 인스턴스를 생성하는 방법입니다. 멀티스레드 환경에서의 동기화 문제가 발생할 수 있어 주의가 필요합니다.
- 장점: 인스턴스가 실제로 필요할 때까지 생성을 미루므로, 초기 메모리 부담이 적습니다.
- 단점: 멀티스레드 환경에서 동기화 처리를 하지 않으면 여러 개의 인스턴스가 생성될 수 있습니다.
아래는 Double-Checked Locking(DCL) 기법을 사용하여 스레드 안전성과 성능을 모두 잡은 표준적인 지연 초기화 구현입니다.
public class DatabaseConnectionPool {
// 1. volatile 키워드로 멀티스레드 환경에서의 가시성 문제 해결
private static volatile DatabaseConnectionPool INSTANCE;
// 2. 생성자를 private으로 선언
private DatabaseConnectionPool() {
System.out.println("DB 커넥션 풀 인스턴스가 생성되었습니다.");
}
// 3. 유일한 인스턴스를 반환하는 메서드
public static DatabaseConnectionPool getInstance() {
// 첫 번째 체크: 인스턴스가 이미 존재하면 동기화 블록에 진입할 필요가 없다. (성능 향상)
if (INSTANCE == null) {
// 두 번째 체크를 위한 동기화 블록
synchronized (DatabaseConnectionPool.class) {
// 인스턴스가 없는 경우에만 새로 생성한다.
if (INSTANCE == null) {
INSTANCE = new DatabaseConnectionPool();
}
}
}
return INSTANCE;
}
}
4. 싱글톤 패턴과 스프링 (Spring Framework)
스프링 프레임워크는 싱글톤 패턴의 가장 강력하고 모범적인 활용 사례입니다.
스프링 컨테이너(IoC Container)가 관리하는 빈(Bean)의 기본 스코프(Scope)가 바로 싱글톤입니다.
개발자가 위와 같이 복잡한 코드를 작성할 필요 없이, 클래스를 단순히 빈으로 등록하기만 하면 스프링이 알아서 다음을 보장해줍니다.
- 해당 타입의 빈을 단 하나만 생성하여 싱글톤 레지스트리에 보관합니다.
- @Autowired 등을 통해 의존성을 주입받을 때, 언제나 그 유일한 인스턴스를 주입해줍니다.
- 스레드 안전성 문제도 대부분 해결해줍니다.
// 개발자는 그냥 평범한 클래스를 만들기만 하면 된다.
@Service
public class OrderService {
public void placeOrder() {
System.out.println("주문을 처리합니다.");
}
}
// 스프링이 OrderService의 인스턴스를 딱 하나만 생성해서 관리한다.
// 아래 두 Controller는 스프링을 통해 동일한 OrderService 인스턴스를 주입받는다.
@RestController
public class OrderControllerA {
@Autowired
private OrderService orderService; // 주입받은 orderService
}
@RestController
public class OrderControllerB {
@Autowired
private OrderService orderService; // 주입받은 orderService (A와 동일한 객체)
}
이처럼 스프링은 싱글톤 패턴의 장점(메모리 효율, 전역적 접근)은 취하면서도, 단점(테스트의 어려움, 강한 결합)을 **의존성 주입(Dependency Injection)**이라는 기술로 멋지게 해결했습니다.
5. 문제 제시 및 답변
개념을 잘 이해했는지 확인해볼 시간입니다! 💡
문제: 애플리케이션의 테마(예: '다크 모드', '라이트 모드')와 폰트 크기 같은 UI 설정을 관리하는 UIConfig 클래스를 설계하려고 합니다. 이 클래스는 애플리케이션 내의 모든 화면에서 동일한 설정 값을 참조해야 합니다. 이 UIConfig 클래스를 설계할 때 싱글톤 패턴을 적용하는 것이 적절할까요? 그렇다면 그 이유는 무엇일까요?
답변:
네, 매우 적절합니다.
이유:
- 유일성 보장: UI 설정은 애플리케이션 전체에서 하나의 상태를 유지하고 공유해야 합니다. 만약 UIConfig의 인스턴스가 여러 개 생성된다면, A 화면은 '다크 모드'인데 B 화면은 '라이트 모드'로 표시되는 등 상태가 일치하지 않는 심각한 문제가 발생할 수 있습니다. 싱글톤 패턴은 인스턴스를 단 하나로 보장하여 이러한 문제를 원천적으로 방지합니다.
- 전역적 접근점: 어느 화면 컴포넌트에서든 UIConfig.getInstance()를 호출하여 현재 설정된 테마나 폰트 크기 값에 손쉽게 접근할 수 있습니다. 이는 상태를 일관되게 관리하는 데 매우 편리합니다.
- 자원 효율성: 설정 정보를 담는 객체를 여러 개 만들 필요 없이 단 하나만 생성하여 사용하므로 메모리 측면에서도 효율적입니다.
싱글톤 패턴은 강력하지만 오용하면 테스트하기 어렵고 유연성이 떨어지는 코드를 만들 수 있습니다. 스프링처럼 좋은 프레임워크가 제공하는 싱글톤 관리 방식을 활용하는 것이 현대적인 개발 방법입니다.
'프로그래밍 > 알고리즘&자료구조&패턴' 카테고리의 다른 글
| 디자인패턴 - 빌더패턴 (1) | 2025.08.18 |
|---|---|
| 디자인패턴 - 팩토리패턴 (1) | 2025.08.18 |
| 자료구조 트리, 이진탐색트리, 해시테이블 (5) | 2025.08.14 |
| 자료구조 배열, 리스트, 링크드 리스트, 스택, 큐 (3) | 2025.08.14 |
| 시간복잡도, 공간복잡도 (2) | 2025.08.14 |