1. 소개
이 빠른 기사에서는 일반 Java에서 Singleton을 구현하는 가장 널리 사용되는 두 가지 방법에 대해 설명합니다.
2. 클래스 기반 싱글톤
가장 널리 사용되는 접근 방식은 일반 클래스를 만들고 다음이 있는지 확인하여 Singleton을 구현하는 것입니다.
- 개인 생성자
- 유일한 인스턴스를 포함하는 정적 필드
- 인스턴스를 얻기 위한 정적 팩토리 메소드
나중에 사용할 수 있도록 정보 속성도 추가합니다. 따라서 구현은 다음과 같습니다.
public final class ClassSingleton {
private static ClassSingleton INSTANCE;
private String info = "Initial info class";
private ClassSingleton() {
}
public static ClassSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
// getters and setters
}
이것은 일반적인 접근 방식이지만 싱글톤을 사용하는 주된 이유인 다중 스레딩 시나리오에서 문제가 될 수 있다는 점에 유의해야 합니다 .
간단히 말해서 패턴의 핵심 원칙을 위반하는 인스턴스가 두 개 이상 발생할 수 있습니다. 이 문제에 대한 잠금 솔루션이 있지만 다음 접근 방식은 이러한 문제를 루트 수준에서 해결합니다.
3. 열거형 싱글톤
앞으로 열거형을 사용하는 또 다른 흥미로운 접근 방식에 대해 논의해 보겠습니다.
public enum EnumSingleton {
INSTANCE("Initial class info");
private String info;
private EnumSingleton(String info) {
this.info = info;
}
public EnumSingleton getInstance() {
return INSTANCE;
}
// getters and setters
}
이 접근 방식은 enum 구현 자체에 의해 직렬화 및 스레드 안전성이 보장되어 내부적으로 단일 인스턴스만 사용 가능하도록 보장하여 클래스 기반 구현에서 지적된 문제를 수정합니다.
4. 사용법
ClassSingleton 을 사용하려면 인스턴스를 정적으로 가져와야 합니다.
ClassSingleton classSingleton1 = ClassSingleton.getInstance();
System.out.println(classSingleton1.getInfo()); //Initial class info
ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");
System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info
EnumSingleton 은 다른 Java Enum처럼 사용할 수 있습니다.
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();
System.out.println(enumSingleton1.getInfo()); //Initial enum info
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");
System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info
5. 일반적인 함정
싱글톤은 믿을 수 없을 정도로 단순한 디자인 패턴이며 프로그래머가 싱글톤을 만들 때 저지를 수 있는 일반적인 실수는 거의 없습니다.
우리는 싱글톤과 관련된 두 가지 유형의 문제를 구분합니다.
- 실존적(싱글톤이 필요한가요?)
- 구현(제대로 구현합니까?)
5.1. 실존적 문제
개념적으로 싱글톤은 일종의 전역 변수입니다. 일반적으로 전역 변수는 피해야 한다는 것을 알고 있습니다. 특히 상태가 변경 가능한 경우에는 더욱 그렇습니다.
우리는 싱글톤을 절대 사용해서는 안된다고 말하는 것이 아닙니다. 그러나 우리는 코드를 구성하는 더 효율적인 방법이 있을 수 있다고 말하고 있습니다.
메서드의 구현이 싱글톤 개체에 의존하는 경우 매개 변수로 전달하지 않는 이유는 무엇입니까? 이 경우 메서드가 의존하는 대상을 명시적으로 보여줍니다. 결과적으로 테스트를 수행할 때 이러한 의존성을 쉽게 조롱할 수 있습니다(필요한 경우).
예를 들어 싱글톤은 종종 응용 프로그램의 구성 데이터(즉, 저장소에 대한 연결)를 포함하는 데 사용됩니다. 전역 개체로 사용하면 테스트 환경에 대한 구성을 선택하기가 어려워집니다.
따라서 테스트를 실행할 때 프로덕션 데이터베이스가 테스트 데이터로 손상되어 거의 허용되지 않습니다.
싱글톤이 필요한 경우 인스턴스화를 다른 클래스(일종의 팩토리)에 Delegation할 가능성을 고려할 수 있습니다.
5.2. 구현 문제
싱글톤이 매우 단순해 보이지만 구현에는 다양한 문제가 있을 수 있습니다. 모든 결과는 클래스의 인스턴스가 둘 이상일 수 있다는 사실입니다.
동기화
위에서 제시한 개인 생성자를 사용한 구현은 스레드로부터 안전하지 않습니다. 단일 스레드 환경에서는 잘 작동하지만 다중 스레드 환경에서는 작업의 원자성을 보장하기 위해 동기화 기술을 사용해야 합니다.
public synchronized static ClassSingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
메서드 선언에서 동기화 된 키워드에 유의하십시오 . 메서드 본문에는 여러 작업(비교, 인스턴스화 및 반환)이 있습니다.
동기화가 없으면 두 스레드가 INSTANCE == null 표현식이 두 스레드 모두에 대해 true 로 평가되고 결과적으로 ClassSingleton 의 두 인스턴스가 생성되는 방식으로 실행을 인터리브할 가능성이 있습니다.
동기화 는 성능에 상당한 영향을 미칠 수 있습니다. 이 코드가 자주 호출되는 경우 지연 초기화 또는 이중 확인 잠금 과 같은 다양한 기술을 사용하여 속도를 높여야 합니다(컴파일러 최적화로 인해 예상대로 작동하지 않을 수 있음). 예제 " Double-Checked Locking with Singleton " 에서 자세한 내용을 볼 수 있습니다 .
여러 인스턴스
싱글톤의 여러 인스턴스로 끝날 수 있는 JVM 자체와 관련된 싱글톤에는 몇 가지 다른 문제가 있습니다. 이러한 문제는 매우 미묘하며 각각에 대해 간략하게 설명하겠습니다.
- 싱글톤은 JVM마다 고유해야 합니다. 이는 분산 시스템 또는 내부가 분산 기술을 기반으로 하는 시스템에서 문제가 될 수 있습니다.
- 모든 클래스 로더는 해당 버전의 싱글톤을 로드할 수 있습니다.
- 아무도 그것에 대한 참조를 보유하지 않으면 싱글톤은 가비지 수집될 수 있습니다. 이 문제로 인해 한 번에 여러 싱글톤 인스턴스가 존재하지는 않지만 다시 만들 때 인스턴스가 이전 버전과 다를 수 있습니다.
6. 결론
이 빠른 사용방법(예제)에서는 핵심 Java만 사용하여 Singleton 패턴을 구현하는 방법과 일관성을 유지하는 방법 및 이러한 구현을 사용하는 방법에 중점을 두었습니다.
이러한 예제의 전체 구현은 GitHub 에서 찾을 수 있습니다 .