728x90
싱글톤이란?
싱글톤 패턴은 하나의 클래스에 대해 단 하나의 인스턴스만 생성되도록 보장하고, 이 인스턴스에 전역적 접근을 제공하는 디자인 패턴입니다. 프로그램 전체에서 동일한 객체를 공유해야 할 때 사용하는 패턴입니다.
싱글톤 패턴이 필요한 이유
- 하나의 리소스에 대한 일관성 유지
- 데이터베이스 연결 객체나 설정 파일 관리 객체처럼 하나만 존재해야 하는 리소스를 관리할 때 사용합니다.
- 여러 객체가 동일한 자원을 동시에 수정하면 데이터 무결성에 문제가 발생할 수 있습니다.
- 메모리 절약 및 성능 최적화
- 동일한 객체를 여러 번 생성하는 대신 하나의 인스턴스를 재사용하므로 메모리 낭비를 줄일 수 있습니다.
- 글로벌 접근 가능성 제공
- 프로그램 어디서든지 하나의 객체에 접근해야 하는 경우에 사용합니다.
싱글톤 패턴의 구조
싱글톤 패턴의 기본 구조는 다음과 같습니다:
- 클래스 생성자를 private으로 선언해 외부에서 객체를 생성할 수 없도록 합니다.
- 클래스 내부에 자신의 유일한 인스턴스를 저장합니다.
- 인스턴스에 접근하는 정적 메서드(getInstance())를 제공합니다.
싱글톤 패턴 구현 방법
1. 기본 싱글톤 패턴 (Lazy Initialization)
인스턴스를 필요할 때 생성합니다.
public class Singleton {
// 클래스 내부에 유일한 인스턴스를 저장할 정적 필드 선언
private static Singleton instance;
// 생성자를 private으로 선언하여 외부에서 인스턴스 생성 불가
private Singleton() {
System.out.println("Singleton instance created");
}
// 정적 메서드로 인스턴스에 접근
public static Singleton getInstance() {
if (instance == null) { // 처음 호출될 때 인스턴스 생성
instance = new Singleton();
}
return instance;
}
}
사용 예시:
public class Main {
public static void main(String[] args) {
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
// 두 객체가 동일한지 확인
System.out.println(obj1 == obj2); // true
}
}
출력:
Singleton instance created
true
- 특징: 첫 번째로 getInstance()가 호출될 때 인스턴스를 생성합니다.
- 문제점: 멀티스레드 환경에서 두 개의 스레드가 동시에 getInstance()를 호출하면 여러 개의 인스턴스가 생성될 위험이 있습니다.
2. Thread-safe Singleton (멀티스레드 환경에서 안전한 싱글톤)
멀티스레드 환경에서는 여러 스레드가 동시에 접근할 때 문제가 발생할 수 있습니다. 이를 해결하기 위해 동기화를 사용합니다.
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("Singleton instance created");
}
// synchronized 키워드로 멀티스레드 환경에서 안전하게 만듦
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
문제점:
- 성능 저하: synchronized 키워드로 인해 모든 스레드가 동시에 접근하지 못하므로 성능이 저하될 수 있습니다.
3. 이중 검증 잠금 (Double-Checked Locking)
성능 문제를 해결하기 위해 이중 검증 잠금(double-checked locking) 방식을 사용합니다.
public class Singleton {
// volatile 키워드로 인스턴스가 완전히 생성되기 전에 참조되는 것을 방지
private static volatile Singleton instance;
private Singleton() {
System.out.println("Singleton instance created");
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) { // 첫 번째 검사
if (instance == null) { // 두 번째 검사
instance = new Singleton();
}
}
}
return instance;
}
}
특징:
- volatile 키워드는 인스턴스가 생성되는 도중에 다른 스레드가 참조하지 않도록 보장합니다.
- 성능과 멀티스레드 안전성을 동시에 확보합니다.
4. Bill Pugh Singleton (권장되는 방식)
내부 정적 클래스를 사용한 싱글톤 구현은 성능과 안정성이 모두 뛰어납니다. 이 방법은 클래스가 로드될 때 초기화되지 않고, 최초로 호출될 때 인스턴스가 생성됩니다.
public class Singleton {
private Singleton() {
System.out.println("Singleton instance created");
}
// 내부 정적 클래스에 유일한 인스턴스를 생성
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
// 정적 메서드로 인스턴스에 접근
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
특징:
- 내부 클래스는 사용될 때만 로드되므로 지연 초기화(Lazy Initialization)를 보장합니다.
- 멀티스레드 환경에서도 안전합니다.
지연 초기화는 필요할 때까지 객체의 초기화를 지연시키는 방법입니다.
즉, 객체나 리소스가 처음으로 사용될 때 인스턴스를 생성하여 불필요한 자원 낭비를 방지합니다.
싱글톤 패턴의 장단점
장점:
- 메모리 절약: 객체를 한 번만 생성하므로 메모리 낭비가 줄어듭니다.
- 일관성 유지: 여러 객체가 아닌 하나의 인스턴스를 공유하므로 데이터의 일관성을 유지할 수 있습니다.
- 전역적 접근: 프로그램 어디서든 동일한 인스턴스에 접근할 수 있습니다.
단점:
- 테스트 어려움: 객체를 하나만 생성하므로 의존성 주입(Dependency Injection)이 어려워 테스트 코드 작성이 복잡해질 수 있습니다.
- 의존성 문제: 하나의 객체가 프로그램 전체에서 사용되므로 클래스 간 강한 결합이 생길 수 있습니다.
- 멀티스레드 이슈: 잘못 구현된 싱글톤은 멀티스레드 환경에서 동시성 문제를 유발할 수 있습니다.
싱글톤 패턴의 사용 사례
- 데이터베이스 연결 객체 (DB Connection Pool)
- 데이터베이스와 연결하는 객체는 여러 개의 인스턴스를 생성할 필요가 없으므로 싱글톤으로 구현합니다.
- 설정 클래스 (Configuration)
- 애플리케이션의 설정 정보는 하나의 인스턴스로 공유하는 것이 바람직합니다.
- 로깅 객체 (Logger)
- 로그 기록을 남기는 객체는 싱글톤으로 구현하여 여러 곳에서 동일한 로그 파일을 사용할 수 있도록 합니다.
정리
싱글톤 패턴은 프로그램 전역에서 하나의 인스턴스만 필요할 때 매우 유용합니다.
다만, 멀티스레드 환경에서는 주의가 필요하며 내부 클래스 방식(Bill Pugh 방식)을 사용하는 것이 성능과 안정성을 모두 보장할 수 있는 좋은 선택입니다.
728x90