싱글톤 패턴은 소프트웨어 엔지니어링에서 가장 잘 알려진 패턴 중 하나이다.
기본적으로 싱글톤은 자신의 단일 인스턴스만 생성할 수 있는 클래스이며 일반적으로 해당 인스턴스에 대한 간단한 액세스를 제공한다.
가장 일반적으로 싱글톤은 인스턴스를 생성할 때 매개변수를 지정하는 것을 허용하지 않는다.
그렇지 않을 경우 인스턴스에 대한 두 번째 요청이지만 다른 매개변수를 사용하면 문제가 발생할 수도 있기 때문이다. (동일한 매개변수를 사용하는 모든 요청에 대해 동일한 인스턴스에 액세스 해야 하는 경우 팩토리 패턴이 더 적합하다.)
아래의 구현 방법은 매개변수가 필요하지 않은 상황에 대해서만 다룬다.
일반적으로 싱글톤의 요구 사항은 느리게 생성된 다는 것에 있다. 즉, 인스턴스가 처음 필요할 때까지 생성되지 않는 것이다.
다음 구현 방버에는 네 가지 공통된 특성이 있다.
이러한 모든 구현 방법은 또한 인스턴스에 액세스 하는 수단으로 공용 정적 속성을 사용한다. 모든 경우에 이 속성은 스레드 안정성이나 성능에 영향을 미치지 않고 메서드로 쉽게 변환될 수 있다.
#1. 스레드로부터 안전하지 않은 방법
// Bad code! Do not use!
public sealed class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton Instance {
get {
if (instance == null) instance = new Singleton();
return instance;
}
}
}
앞에서 암시했듯이 위의 내용은 스레드로부터 안전하지 않다.
두 개의 서로 다른 스레드가 모두 테스트를 평가하고 조건문이 성립되는 것을 확인 한 후 둘 다 인스턴스를 생성하여 싱글톤 패턴을 위반할 수 있다.
실제로 인스턴스는 표현식이 평가되기 전에 이미 생성되었을 수 있지만 메모리 모델은 적절한 메모리 장벽을 통과하지 않는 한 인스턴스의 새 값이 다른 스레드에서 볼 수 있다고 보장되지 않는다.
#2. 단순 스레드에 안정적인 방법
public sealed class Singleton {
private static Singleton instance = null;
private static readonly object padlock = new object();
Singleton(){}
public static Singleton Instance {
get {
lock (padlock) {
if (instance == null) instance = new Singleton();
return instance;
}
}
}
}
위 구현은 스레드로부터 안전하다. 스레드의 공유 객체에 대한 잠금을 해제한 다음, 인스턴스를 생성하기 전에 인스턴스가 생성되었는지 여부를 확인한다. 이는 메모리 장벽 문제를 처리하고 (잠금은 잠금 획득 후에 모든 읽기가 논리적으로 발생하도록 하고 잠금 해제는 잠금 해제 전의 모든 쓰기가 논리적으로 발생하는지 확인하기 때문) 오직 하나의 스레드만 인스턴스를 생성하도록 한다.
한 번에 하나의 스레드가 코드의 해당 부분에 있을 수 있다.
두 번째 스레드가 들어갈 때 첫 번째 스레드가 인스턴스를 생성하므로 조건문은 false로 평가된다.
단, 인스턴스가 요청 될 때마다 잠금이 획득되므로 성능이 저하된다.
typeof (Singleton)이 구현의 일부 버전처럼 잠그는 대신 클래스에 대해 비공개적인 정적 변수 값을 잠근다. 다른 클래스가 액세스하고 잠글 수 있는 개체 (예:유형)를 잠그면 성능 문제와 교착 상태가 발생할 위험이 있다.
가능하면 잠글 목적으로 특별이 생성된 개체 또는 특정 목적 (예:대기열 대기/펄스)을 위해 잠글 문서에 대해서만 잠근다.
일반적으로 이러한 개체는 사용된 클래스에 대해 비공개여야 한다. 이는 스레드로부터 안전한 응용 프로그램을 훨씬 쉽게 작성하는 데 도움이 된다.
#3. 이중 확인 잠금을 사용하여 스레드 안정성을 시도하는 방법
// Bad code! Do not use!
public sealed class Singleton {
private static Singleton instance = null;
private static readonly object padlock = new object();
Singleton(){}
public static Singleton Instance {
get {
if (instance == null) {
lock (padlock) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
}
이 구현은 매번 잠금을 해제할 필요 없이 스레드로부터 안전하도록 시도한다.
단, 위 패턴에는 네 가지 단점이 있다.
#4. Lazy는 아니지만 잠금을 사용하지 않고 스레드로부터 안전한 방법
public sealed class Singleton {
private static readonly Singleton instance = new Singleton();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton(){}
private Singleton(){}
public static Singleton Instance {
get {
return instance;
}
}
}
매우 간단하지만 스레드로부터 안전하고 Lazy 한 방법이다.
C# 정적 생성자는 클래스의 인스턴스가 생성되거나 정적 멤버가 참조될 때만 실행되고 AppDomain 당 한 번만 실행되도록 지정된다. 새로 생성되는 유형에 대한 이 검사는 무슨 일이 있어도 실행되어야 하므로 이전 방법과 같이 검사를 추가하는 것보다 빠르다.
단, 위의 방법도 약간의 문제가 존재한다.
#5. 완전히 Lazy한 방법
public sealed class Singleton {
private Singleton(){}
public static Singleton Instance {
get {
return Nested.instance;
}
}
private class Nested {
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested(){}
internal static readonly Singleton instance = new Singleton();
}
}
인스턴스화는 중첩 클래스의 정적 멤버에 대한 첫 번째 참조에 의해 트리거 되며, 이는 구현이 완전히 Lazy 하지만 이전 구현의 모든 성능에 대해서 이점이 있음을 의미한다.
중첩된 클래스가 둘러싸는 클래스의 private 멤버에 엑세스 할 수 있지만 instance에서 내부여야 할 필요가 있다.
단, 클래스 자체가 private이기 때문에 다른 문제는 발생하지 않는다. 그러나 코드는 인스턴스화를 Lazy 하게 만들기 위해 조금 더 복잡하다.
#6. Lazy<T>를 사용한 방법
public sealed class Singleton {
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance {
get {
return lazy.Value;
}
}
private Singleton(){}
}
.NET 4 이상을 사용하는 경우 System.Lazy<T> 유형을 사용하여 정말 간단하게 구현이 가능하다.
Singleton 생성자를 호출하는 생성자에 대리자를 전달하기만 하면 된다. 이는 람다식으로 쉽게 수행된다.
위 방법은 간단하고 성능이 좋다. 또한 필요한 경우 IsValueCreated 속성을 사용하여 인스턴스가 아직 생성되었는지의 여부를 확인할 수도 있다.
'공부중 > 개념알기' 카테고리의 다른 글
[Java] 기본형 데이터 타입 (0) | 2022.08.29 |
---|---|
전문통신 vs API (0) | 2022.08.03 |
HTTP(Hyper Text Transfer Protocol) (0) | 2022.08.03 |
[JAVA] 싱글톤 패턴 (0) | 2022.07.29 |