본문 바로가기

ComputerScience/DesignPattern

[DesignPattern] Singleton Pattern

싱글톤 패턴 (Singleton Pattern)

이미지 출처: https://en.wikipedia.org/wiki/Singleton_pattern


Singleton 패턴은 소프트웨어 디자인 패턴 중 하나로, 클래스의 인스턴스화를 단일 인스턴스로 제한하는 소프트웨어 디자인 패턴입니다. "Gang of Four" 디자인 패턴 중 하나로, 객체 지향 소프트웨어에서 반복적으로 발생하는 문제를 해결하는 방법을 설명합니다. 이 패턴은 시스템 전체에 걸쳐 작업을 조정해야 할 때 유용합니다.

Singleton 패턴을 사용하면 객체가 다음을 수행할 수 있습니다.

1. 하나의 인스턴스만 가지도록 보장
2. 해당 인스턴스에 쉽게 액세스
3. 인스턴스의 생성을 제어 (예: 클래스의 생성자를 숨김)

이 용어는 수학적인 개념인 'singleton'에서 유래되었습니다.


싱글톤은 종종 global 변수와 비교하여 선호되는데, 그 이유는 global namespace (또는 해당 포함 네임스페이스)를 오염시키지 않기 때문입니다.

또한 많은 언어에서 global 변수는 항상 리소스를 소비하는 반면, 싱글톤은 지연 할당(lazy allocation) 및 초기화를 허용하며 항상 리소스를 소비하지 않습니다.

Logging 은 싱글톤의 흔한 사용 사례 중 하나입니다. 메시지를 기록하려는 모든 객체는 통일된 액세스 지점이 필요하며 개념적으로는 단일 소스에 기록해야 하기 때문입니다.

고전적인 싱글톤 패턴 생성 방법

public class Singleton {
	private static Singleton uniqueInstance;
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here
	public String getDescription() {
		return "I'm a classic Singleton!";
	}
}

 

private 생성자를 통해 객체를 외부에서 생성하는 것을 막고, getInstance를 통해 객체를 전달 받습니다.
이때, 이미 생성했던 객체가 있다면 이미 생성했던 객체를 반환합니다.
이미 생성했던 객체는 static 변수이기 때문에 heap 메모리 부분에 저장되어 프로그램이 끝날 때까지 유지됩니다.

 

이 때 발생할 수 있는 문제점은, 멀티스레드 환경에서 getInstance()의 if 문을 통과가 여러번되고 그 이후에 new Singleton()이 불린다면, 의도치 않게 여러 인스턴스가 생성된다는 것 입니다.

해결 방법

방법 1: getInstance() 동기화하기

public class Singleton {
	private static Singleton uniqueInstance;
 
	// other useful instance variables here
 
	private Singleton() {}
 
	public static synchronized Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here
	public String getDescription() {
		return "I'm a thread safe Singleton!";
	}
}

 

이 방법은 synchronized 키워드를 사용하여, getInstance() 메서드에 접근을 하나의 스레드만 할 수 있으므로 위 문제는 해결되지만, 메서드를 동기화 하면 성능이 100배 정도 저하된다.

방법 2: 인스턴스가 필요할 때 생성하지 말고 처음부터 만든다. (Eager initialization)

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		return uniqueInstance;
	}
	
	// other useful methods here
	public String getDescription() {
		return "I'm a statically initialized Singleton!";
	}
}

이 방법을 사용하면 JVM에게 클래스가 로드될 때 싱글톤의 고유한 인스턴스를 생성하도록 의존합니다.

JVM은 어떤 스레드도 정적인 uniqueInstance 변수에 액세스하기 전에 해당 인스턴스가 생성될 것을 보장합니다.

방법 3: Lazy initialization with DCL(Double Checked Locking)을 사용한다.

public class Singleton {
	private volatile static Singleton uniqueInstance;
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			synchronized (Singleton.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

Lazy initialization은 객체의 생성, 값의 계산, 비싼 프로세스와 같은 시간이 오래 걸리는 작업을 지연시키는 전략으로, 처음 필요할 때만 수행하는 것을 말합니다.

위 방식은 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.
하지만 volatile을 사용하여 성능 측면에서 불리하며, 이 방식은 자바 1.4 이전 버전에서는 사용할 수 없습니다.

 

volatile?

자바에서는 멀티스레드 환경에서 여러 스레드가 동시에 하나의 변수에 접근할 때 데이터의 일관성이 깨질 수 있습니다. 이를 해결하기 위해 변수를 volatile로 선언하면 해당 변수의 값을 읽거나 쓸 때 CPU 캐시가 아니라 메인 메모리를 사용하여 스레드 간의 가시성을 보장합니다.
`volatile` 변수는 다른 스레드에서의 변경을 즉시 볼 수 있고, 변경한 값을 다른 스레드와 공유할 때 사용됩니다. 

 

비판

싱글톤은 종종 불필요하게 애플리케이션에 전역 상태를 도입하는 Anti-pattern 으로 간주되어 일부에서 비판받고 있습니다.

이는 다른 객체들이 싱글톤에 대한 의존성을 가질 수 있으며, 의존성이 실제로 존재하는지를 확인하기 위해 구현 세부사항을 분석해야 한다는 것을 의미합니다.

이러한 증가된 결합은 유닛 테스트에서 어려움을 일으킬 수 있습니다.

이에 따라 싱글톤을 사용하는 추상화에 제약이 생기며, 여러 인스턴스를 동시에 사용하는 것을 방지하는 등의 제약사항이 발생할 수 있습니다.

또한 싱글톤은 단일 책임 원칙(SRP)을 위반합니다. 이는 싱글톤이 자체적으로 고유성을 강제하면서 일반 기능을 수행하기 때문입니다.

참고 자료

https://en.wikipedia.org/wiki/Singleton_pattern

 

Singleton pattern - Wikipedia

From Wikipedia, the free encyclopedia Design pattern in object-oriented software development A class diagram exemplifying the singleton pattern. In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of

en.wikipedia.org

https://github.com/IT-Book-Organization/HeadFirst-DesignPattern/blob/main/Chapter_05/README.md