1. 배고픈 스타일의 싱글톤 클래스
}
개인 정적 싱글턴 인스턴스 = new Singleton();
개인 정적 싱글턴 getInstance(){
반환 인스턴스;
}
}
특징: 사전에 Hungry 스타일 인스턴스화, 게으른 스타일에서는 멀티스레딩 문제가 없지만 getInstance() 호출 여부에 관계없이 메모리에 인스턴스가 있습니다.
2. 내부 클래스 싱글턴 클래스
}
개인 클래스 SingletonHoledr(){
개인 정적 싱글턴 인스턴스 = new Singleton();
}
개인 정적 싱글턴 getInstance(){
SingletonHoledr.instance를 반환합니다.
}
}
특징: 내부 클래스에서는 지연 로딩이 구현됩니다. getInstance()를 호출할 때만 메모리에 고유한 인스턴스가 생성됩니다. 이는 또한 지연 스타일의 멀티스레딩 문제를 해결하는 것입니다. .
3. 게으른 싱글톤 클래스
}
비공개 정적 싱글톤 인스턴스;
공개 정적 싱글턴 getInstance(){
if(인스턴스 == null){
반환 인스턴스 = new Singleton();
}또 다른{
반환 인스턴스;
}
}
}
특징: 게으른 스타일에는 스레드 A와 B가 있습니다. 스레드 A가 라인 8로 실행되면 스레드 B로 점프합니다. B도 라인 8로 실행되면 두 스레드의 인스턴스가 비어 있으므로 두 가지 예가 생성됩니다. . 해결책은 다음을 동기화하는 것입니다.
동기화는 가능하지만 효율적이지 않습니다.
}
비공개 정적 싱글톤 인스턴스;
공개 정적 동기화 싱글톤 getInstance(){
if(인스턴스 == null){
반환 인스턴스 = new Singleton();
}또 다른{
반환 인스턴스;
}
}
}
전체 getInstance가 전체 "중요 섹션"이기 때문에 이와 같은 프로그램을 작성하는 데 오류는 없지만 효율성은 매우 낮습니다. 왜냐하면 우리의 목적은 실제로 처음으로 인스턴스를 초기화할 때만 잠그고 그런 다음 인스턴스를 사용할 때 스레드 동기화가 전혀 필요하지 않습니다.
그래서 똑똑한 사람들은 다음과 같은 접근 방식을 생각해 냈습니다.
이중 확인 잠금 쓰기 방법:
public static Singleton getSingle(){ //이 메소드를 통해 외부 객체를 얻을 수 있습니다.
if(단일 == null){
동기화됨(Singleton.class) { //동시에 하나의 객체만 이 동기화된 블록에 액세스할 수 있도록 보장합니다.
if(단일 == null){
싱글 = 새로운 싱글턴();
}
}
}
return Single; //생성된 객체를 반환합니다.
}
}
아이디어는 매우 간단합니다. 즉, 코드가 올바르고 효율적이도록 인스턴스를 초기화하는 코드 부분만 동기화(동기화)하면 됩니다.
이는 소위 "이중 확인 잠금" 메커니즘입니다(이름에서 알 수 있듯이).
불행히도 이러한 작성 방식은 많은 플랫폼과 최적화 컴파일러에서 잘못되었습니다.
그 이유는 다른 컴파일러에서 코드 인스턴스 = new Singleton() 줄의 동작을 예측할 수 없기 때문입니다. 최적화 컴파일러는 다음과 같이 instance = new Singleton()을 합법적으로 구현할 수 있습니다.
1. 인스턴스 = 새 엔터티에 메모리를 할당합니다.
2. Singleton 생성자를 호출하여 인스턴스 멤버 변수를 초기화합니다.
이제 스레드 A와 B가 getInstance를 호출하고 있다고 상상해 보십시오. 스레드 A가 먼저 들어가서 1단계가 실행될 때 CPU에서 쫓겨납니다. 그런 다음 스레드 B가 들어가고 B가 보는 것은 인스턴스가 더 이상 null(메모리가 할당됨)이 아니므로 안심하고 인스턴스를 사용하기 시작하지만 이것은 잘못된 것입니다. 이 순간 인스턴스의 멤버 변수는 여전히 기본값이기 때문입니다. A는 아직 2단계를 실행하여 인스턴스 초기화를 완료할 시간이 없습니다.
물론 컴파일러는 다음과 같이 구현할 수도 있습니다.
1. temp = 메모리 할당
2. 임시 생성자를 호출합니다.
3. 인스턴스 = 임시
컴파일러가 이렇게 동작하면 문제가 없을 것 같지만 사실은 그리 간단하지 않습니다. 특정 컴파일러가 어떻게 하는지 알 수 없기 때문입니다. 이 문제는 Java의 메모리 모델에 정의되어 있지 않기 때문입니다.
이중 확인 잠금은 기본 유형(예: int)에 적용 가능합니다. 분명히 기본 유형은 생성자를 호출하지 않기 때문입니다.