Table of Contents
소개: Multi-Threaded Engineering Application의 Singleton 패턴
단일 시스템은 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 단일 시스템의 구성 요소입니다.
이 문서는 가장 일반적인 실수 개발자가 멀티 스레드 환경에서 Singleton 패턴을 구현할 때 확인을 검토하고, 그 결과적으로 원인을 설명하고, 그들을 방지하기 위해 모범 사례와 패턴의 포괄적 인 세트를 제공합니다. 또한 자바에서 실용적인 코드 예제를 포함, C++ 및 C#에 해당 패턴에 대한 참조, 그리고 더 읽기에 대한 외부 리소스를 권장합니다.
Singleton 구현에 공통된 실수
경험이 풍부한 개발자는 동시 시스템에 단일 톤을 구현할 때 함정을 떨어질 수 있습니다. 아래는 위험이 있는 이유에 대한 설명과 함께 가장 빈번한 오류입니다.
1. Constructor 개인을 만들기
단일턴의 기초는 외부 즉석을 방지하는 개인 구성 요소입니다. 구성자가 접근 가능 (공공, 보호 또는 패키지 개인) 인 경우, 모든 스레드는 단일 톤 계약을 깨는 새로운 인스턴스를 만들 수 있습니다. 다중 스레드 코드에서, 이것은 클래스가 재발하고 구성자가 실수로 변경 될 때, 또는 클래스가 하위 분류 될 때, (단일은 일반적으로 디스크를 정렬하는 것은) 개인 식별자 (비밀)를 선언하고, 개인 식별자 (비밀)를 선언하고, 개인 식별자 (비밀)를 준수해야합니다. (비밀은 개인 식별자 (비밀), 개인 식별자 (비밀), 또는 (비밀)
2. 실 안전 취급에 직면
단일 스레드 환경에서, 간단한 게으른 초기화는 잘 작동:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
그러나 다중 스레드 응용 프로그램에서 두 개 이상의 스레드는 연속적으로 을 입력 할 수 있습니다 어떤 스레드가 생성 된 경우. 각 스레드는 그 자체 객체를 생성하는 것을 진행합니다. 이것은 고전적인 race condition]이며 여러 인스턴스에서 결과를 리드 할 수 있습니다.
3. Proper 동기화 없는 Lazy 초기화 사용하기
실 안전에 대한 필요성을 인식하는 개발자는 종종 동기화를 자동적으로 추가합니다. 예를 들어, 전체 메서드를 동기화하지만 성능 병목을 소개합니다.
public static synchronized Singleton getInstance() { ... }
모든 호출 인수 및 릴리즈 잠금, 심지어 이후 인스턴스는 이미 생성. 높은 콘텐츠 시나리오에서, 이 오버 헤드 심각 하 게 degrade 처리 할 수 있습니다. 더 나은 접근은 사용 하는 것입니다 double-checked locking] (아래에 처리), 하지만 패턴은 제대로 구현 하지 않는 경우 pitfalls.
4. 동기화
동기화는 많은 형태로 제공됩니다: ] 방법, 블록, , , 등. 상응도의 응집을 받지 않는 잠금을 해제하는 것은 불필요한 콘텐츠에 대한 납치 될 수 있습니다. 일부 엔지니어링 응용 프로그램 (예 : 엄격한 대기 예산이있는 실시간 시스템)에서, 심지어 몇 백 나노 잠금을 해제하는 것은 여전히 안전하지 않습니다. 그러나 안전은 여전히 안전하지 않는 한, 안전은 여전히 안전하지 않습니다.
5. volatile 키워드를 무시
Java, C#, C++(])와 같은 언어에서는, ]volatile] 키워드(또는 이와 동등한)는 멀티 스레드 코드에 대한 정확한 가시성을 위해 필수적입니다. 이를 제외하고, 컴파일러 또는 CPU는 명령 지침을 재주문할 수 있으며, 한 스레드에 의해 만들어진 변경은 다른 것으로 볼 수 없습니다. 이중 검사된 잠금 패턴에서, Singleton 인스턴스를 선언하지 못했습니다. 는 가장 위험하고 가장 위험한 행동을 볼 수 있습니다.
Thread-Safe Singleton 구현을위한 모범 사례
이러한 pitfalls를 방지하기 위해이 입증 된 전략을 따르십시오. 각 접근법은 스레드 안전, 성능 및 단순성을 요구합니다.
개인 구조 및 정적 Instance
초기화 전략에 관계없이, 생성자는 개인이어야합니다. 단일 톤 인스턴스는 정적 필드에 저장되어야합니다. 어떤 방법으로 생성기를 노출하지 않으며 클래스 를 Java (또는 ])에서 하위 클래스를 방지하기 위해 고려하십시오.
동기화된 블록만 필요하면
게으른 초기화에 대 한, 이중 검사된 잠금 패턴 동기화 머리 위 감소:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance; // Local variable for performance
if (result == null) {
synchronized (Singleton.class) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}
}
이 코드에서 체크 ] 외부 동기화 블록은 이미 존재하는 경우 잠금 오버 헤드를 방지합니다. 내부 체크는 하나의 스레드가 인스턴스를 만듭니다. 키워드는 명령을 방지하고 할당 이 다른 스레드에 완전히 가시한다는 것을 보장합니다. 우리가 성능에 대한 로컬 변수에 예를 캐시하는 참고. 이 패턴은 Java 5 + ( 적절한 메모리 모델)에서 올바른 것입니다 (CLT : 18 + CLT) [FLT]]와 유사한 순서 [FLT]].
Eager 초기화
단일 톤이 항상 필요로하고 창조가 싸고 있다면, eager 초기화는 가장 간단한 실 안전 접근법입니다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Class 로딩은 JVM에 의해 동기화되므로 추가 조정이 필요하지 않습니다. 그러나 이것은 리소스 기반 시스템에서 undesirable일 수 있는 class load time에 인스턴스를 생성하거나 싱글톤이 아직 사용할 수 없는 runtime 구성에 따라 달라집니다.
정적 홀더 패턴 (Initialization-on-demand)
이 패턴은 명시된 동기화없이 스레드 안전과 함께 게으른 초기화를 결합합니다:
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
] 클래스는 ]가 처음 호출되고 JVM은 클래스 로딩 중에 정적 필드의 안전한 출판을 보장합니다. 이것은 Java 싱글턴에 가장 우아한 솔루션으로 널리 알려져 있습니다.
Enum 기반 싱글 톤 (Java)
Joshua Bloch의 Effective Java는 enum을 사용하는 것이 좋습니다.
public enum Singleton {
INSTANCE;
// methods and fields
}
Enum Constants는 불확실히 이며, Java 언어는 enum 인스턴스가 일련화 또는 반사 공격의 밑에 한 번만 생성된다는 것을 보증합니다. 이것은 실 안전과 간결 모두입니다. 그러나 enums는 클래스를 확장 할 수 없습니다 (만 구현 인터페이스), 그래서 그들은 모든 사용 사례에 적합하지 않습니다.
C++ 및 C#의 동등한 패턴
C++에서 Meyer의 Singleton (현지적 초기화)는 C++11 이후의 실안전입니다.
Singleton& getInstance() {
static Singleton instance;
return instance;
}
C#에서, 클래스는 내장 스레드 안전 게으른 초기화를 제공합니다:
public class Singleton {
private static readonly Lazy<Singleton> _lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance => _lazy.Value;
}
공학 응용 분야의 테스트 및 고려
단일톤 패턴은 종종 하드웨어 드라이버, 구성 설정, 스레드 풀, 또는 로깅 서비스와 같은 공유 리소스를 관리합니다. 멀티 스레드 테스트에서 같은 싱글톤을 테스트하면주의적인 디자인이 필요합니다. 다음을 고려하십시오.
- Make Singletons testable] 를 사용하여 인스턴스를 재설정하는 방법을 제공 (예를 들어, 보호 ] 의 방법 테스트에 사용) 또는 인터페이스를 통해 의존성을 주사. 많은 현대 응용 프로그램은 수명주기를 관리하는 종속 주입 프레임 워크의 호의에 Singletons altogether를 방지합니다.
- Performance profiling 실시간 또는 고주파 시스템에서: 동기화의 오버헤드를 측정합니다. 일부 경우에, (C#) 또는 (C++)를 사용하여 잠금이 없는 싱글톤을 사용할 수 있습니다.
- Distributed system은 프로세스 전반에서 고유한 단일턴을 요구한다. 클러스터 전체 싱글턴을 필요로 하는 경우, 외부 코디(예:, 데이터베이스, ZooKeeper, 또는 리더 선거)를 사용한다.
- Reflection 및 serialization은 Singletons를 깰 수 있습니다. Java serialization에서 를 사용하며, 가 이미 설정된 경우 생성자에 예외를 던지는 반사적 순간을 방지합니다.
관련 기사
단일톤 패턴은 소프트웨어 엔지니어의 툴박스에 귀중한 도구이지만, 멀티 스레드 환경에서의 구현은 엄격한 주의를 기울여야 합니다. 비 개인 구성 요소, 누락된 동기화, 불투명한 사용, 과합성화, 과합성화와 같은 일반적인 실수를 이해하고 피함으로써, 더욱 강력한 고성능 싱글톤을 생성할 수 있습니다. 이중 검사된 잠금, 정적 홀더 및 EN-um#는 각 언어의 안전성을 향상시키고, 더욱 견고한 환경에서의 단일 계층화 기능을 제공합니다. 이 기능은 C++++의 단일 톤 패턴을 통해 더욱 견고하고, 더 높은 수준의 단일 톤을 제공합니다.
더 많은 연구에 대한 다음 리소스를 참조 :
궁극적으로, 최고의 단일 톤 구현은 당신의 요구 사항에 가장 간단한 것 이다. 의심의 여지없이, eager 초기화 또는 정적 홀더 패턴을 선호, 항상 콘텐츠 아래 정확한 검증에 동시 단위 테스트를 작성.