디자인 패턴 & OOP
[디자인 패턴] 싱글톤 패턴(Singleton Pattern)
cloud-grace
2024. 5. 18. 18:42
싱글톤 패턴(Singleton Pattern)이란?
싱글톤 패턴은 디자인 패턴(Design Pattern) 중 생성 패턴(Creational Pattern)이다.
생성 패턴 : 객체의 생성과 관련된 패턴이며, 객체의 생성 절차를 추상화하는 패턴
객체를 생성 및 합성하는 방법과 객체의 표현 방법과 시스템을 분리한다.
GoF 디자인 패턴에 의하면 싱글톤 패턴은 어떤 클래스의 인스턴스는 하나임을 보장하고 어디서든 참조할 수 있도록 한다.
사용 목적은 단 하나만 생성하고 그 인스턴스를 사용하기 위해서이다. 주로 공통된 객체를 여러 개 생성해서 사용하는 Database Connection Pool(DBCP), Thread Pool, Device 설정, 로그 기록 객체, 스프링의 Bean 등의 경우, 인스턴스를 여러 개 만든다면 자원을 낭비하게 되고 버그가 발생할 수 있다. 따라서 프로그램 내에서 하나로 공유를 해야하는 객체가 존재할 때, 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용하도록 할 때 활용된다.
싱글톤 패턴 : '하나'의 인스턴스만 생성하여 사용하는 디자인 패턴이다. 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고, 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 즉, 애플리케이션이 시작될 때, 어떤 클래스가 최초에 한 번만 메모리를 할당(static)하며 해당 메모리에 인스턴스를 만들어 사용하는 패턴이다.
싱글톤 패턴의 장단점
장점
- 메모리 측면 : 한 개의 인스턴스만을 고정 메모리 영역에 생성하고 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다.
- 속도 측면 : 생성된 인스턴스를 사용할 때는 속도 측면에서 새로 생성할 때보다 빠르다.
- 데이터 공유 측면 : 전역으로 사용하는 인스턴스이므로 다른 여러 클래스에서 데이터를 공유하기에 좋다.
단점
- 객체 역할이 복잡한 경우 : 싱글톤 객체가 혼자 많은 것을 하고 많은 데이터를 공유하게 해주면, 해당 싱글톤 객체를 사용하는 객체 간의 결합도가 높아진다. 따라서 객체지향 설계 원칙인 '개방-폐쇄 원칙' 에 어긋난다. 따라서 유지보수 및 테스트 진행에 어려움이 발생한다.
- 멀티 스레드 환경인 경우 : 만약 멀티 스레드 환경에서 동기화 처리를 하지 않았다면, 인스턴스가 여러 개 생성될 수도 있다. 즉, 반드시 싱글톤을 써야하는 경우가 아니라면 사용하지 않는 것이 좋다.
싱글톤 패턴 구조
싱글톤 패턴 구현
- 싱글톤 패턴
public class Singleton {
// 정적 변수로 1개만 존재해야 하는 유일한 인스턴스를 저장한다.
private static Singleton instance;
// private 생성자: 외부에서 인스턴스를 생성을 막는다.
private Singleton() {
}
// 정적 메서드로 유일한 인스턴스를 반환한다.
// 외부에서는 getInstance()로 인스턴스를 반환한다.
public static Singleton getInstance() {
// 인스턴스가 null일 때만 생성한다.
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 싱글톤 패턴 생성 예제
public class Main {
public static void main(String[] args) {
// Singleton 클래스의 유일한 인스턴스를 얻음
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
// 출력
System.out.println(singleton1); // 출력 : singleton.Singleton@2d98a335
System.out.println(singleton2); // 출력 : singleton.Singleton@2d98a335
}
}
Multi-Thread에서의 싱글톤 패턴 문제점
[1] 여러 개의 인스턴스 생성의 문제
- Multi-Thread 환경에서 인스턴스가 없는 상황에 동시에 getInstance() 메소드를 실행한다면 각각 새로운 인스턴스를 생성할 수 있다.
// 정적 메서드로 유일한 인스턴스를 반환한다.
// 외부에서는 getInstance()로 인스턴스를 반환한다.
public static Singleton getInstance() {
// 인스턴스가 null일 때만 생성한다.
if (instance == null) {
instance = new Singleton();
}
return instance;
}
[2] 변수 값의 일관성 문제
- Multi-Thread 환경에서 increment() 메소드를 동시에 실행한다면 일관되지 않는 값들이 만들어질 수도 있다.
public class Singleton {
private static Singleton instance;
private static int count = 0;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static void increment() {
count++;
}
}
싱글톤 패턴 해결 방법
[1] 정적 변수 선언에서 인스턴스 생성하기
- static 변수로 Singleton 인스턴스를 생성하는 방법으로 해결한다.
- getInstance()로 Multi-Thread 환경에서도 다른 객체들이 하나의 인스턴스를 공유할 수 있다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
[2] synchronized 사용하기
- synchronized 키워드를 통해 동시성 문제를 해결한다.
- 하지만 synchronized는 Thread-Safe 보장을 위해 성능 저하를 크게 발생시키기 때문에 권장되지는 않는다.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
[3] Holder 방식으로 싱글톤 패턴 구현하기
- 싱글톤 패턴의 초기화 문제를 해결하기 위해 'Holder' 방식을 사용한다.
- 초기화 책임을 JVM에게 넘기며 클래스 로딩 시점에서 싱글톤 인스턴스가 안전하게 초기화된다.
- SingletonHolder 내부 정적 클래스는 Singleton 인스턴스를 정적 필드로 가지고 있다.
- 이 필드는 클래스가 로딩될 때 초기화되며, Singleton 클래스가 로딩될 때까지 로드되지 않으므로 인스턴스가 필요할 때까지 초기화되지 않는다.
- getInstance() 메서드는 SingletonHolder 클래스의 INSTANCE 필드를 반환한다.
- 클래스 로딩 시점에 인스턴스가 생성되므로 스레드 안전성을 보장한다.
- Holder 방식은 코드가 간결하고 이해하기 쉬우며 불필요한 동기화를 피할 수 있고 지연 초기화를 지원하여 효율적이다.
- 실제로 싱글톤 패턴을 이 방식으로 가장 많이 사용한다고 한다.
public class Singleton {
private Singleton() {
}
// 내부 정적 클래스가 싱글톤 인스턴스를 홀드한다.
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 외부에서 싱글톤 인스턴스를 접근할 수 있는 메서드
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}