모두 싱글 톤 모델에 익숙하며 왜 게으르고, 배가 고프다는지 알고 있지만 싱글 톤 패턴에 대한 철저한 이해가 있습니까? 오늘 나는 당신의 이해와 다를 수 있습니다.
간단한 작은 예는 다음과 같습니다.
// 단순한 게으른 공개 클래스 싱글 톤 {// 싱글 톤 인스턴스 변수 개인 정적 싱글 톤 인스턴스 = null; // 생성자 개인 싱글 톤 ()을 통해 외부 클래스를 인스턴스화 할 수 없도록 개인 구조 방법을 확보 할 수 없음 {} // 싱글 톤 인스턴스 가져 오기 공개 정적 싱글 톤 getInstance () {if (instance == null) {instance = new Singleton (); } system.out.println ( "나는 간단한 게으른 싱글 톤입니다!"); 반환 인스턴스; }}멀티 스레딩의 경우 위 코드가 안전하지 않다는 것을 쉽게 알 수 있습니다. 두 스레드가 if (instance == null)에 들어가면 두 스레드 모두 인스턴스가 비어 있다고 판단하고 두 개의 인스턴스가 얻어집니다. 이것은 우리가 원하는 싱글 톤이 아닙니다.
다음으로, 우리는 잠금을 사용하여 싱글 톤의 구현을 보장하기 위해 상호 배제를 달성합니다.
// 동기 방법 게으른 공개 클래스 싱글 톤 {// 싱글 톤 인스턴스 변수 개인 정적 싱글 톤 인스턴스 = null; // 생성자 개인 싱글 톤 ()을 통해 외부 클래스를 인스턴스화 할 수 없도록 개인 구조 방법을 확보 할 수없는 싱글 톤 인스턴스 가져 오기 공개 정적 동기화 된 싱글 턴 getInstance () {if (instance == null) {instance = new Singleton (); } system.out.println ( "나는 동기식 방법 Lazy Singleton!"); 반환 인스턴스; }}동기화 된 추가는 스레드 안전을 보장하지만 이것이 가장 좋은 방법입니까? 이런 식으로 GetInstance () 메소드를 호출 할 때마다 잠겨있을 것이기 때문에, 우리는 처음으로 GetInstance ()를 호출 할 때만 잠겨 있어야합니다. 이것은 분명히 프로그램의 성능에 영향을 미칩니다. 우리는 계속 더 나은 방법을 찾습니다.
분석 후 인스턴스 = New Singleton ()이 스레드 상호 배제임을 확인함으로써 만 스레드 안전을 보장 할 수 있으므로 다음 버전을 사용할 수 있습니다.
// Double Lock Lazy Public Class Singleton {// 싱글 톤 인스턴스 변수 개인 정적 싱글 톤 인스턴스 = null; // 생성자 개인 싱글 톤 () {} // 싱글 톤 인스턴스 가져 오기 공개 정적 싱글 턴 getInstance () {if (instance == null) {synchronized (singleton.class) {if (instance == null) {new Singleton (); }}} system.out.println ( "나는 더블 잠금 게으른 싱글 톤입니다!"); 반환 인스턴스; }} 이번에는 스레드 안전 문제를 해결할뿐만 아니라 GetInstance () 호출 할 때마다 잠금이 추가되지 않아 성능 저하가 발생합니다. 완벽한 솔루션처럼 보입니다. 실제로 그런 식입니까?
불행히도, 사실은 우리가 생각했던 것만 큼 완벽하지 않습니다. Java 플랫폼 메모리 모델에는 "외부 쓰기"라는 메커니즘이 있습니다. 이 메커니즘은 이중 점검 잠금 방법의 고장을 일으키는 것이이 메커니즘입니다. 이 문제의 핵심은 위의 코드의 5 행에 있습니다. instance = new Singleton (); 이 라인은 실제로 두 가지를 수행합니다. 1. 생성자를 호출하고 인스턴스를 만듭니다. 2.이 인스턴스를 인스턴스 변수 인스턴스에 할당하십시오. 그러나 문제는 JVM 의이 두 단계가 주문을 보장하지 않는다는 것입니다. 즉. 생성자를 호출하기 전에 인스턴스가 비 인식으로 설정되었을 수 있습니다. 함께 분석하겠습니다.
두 개의 스레드 a와 b가 있다고 가정합니다
1. a.
2. 현재 인스턴스가 비어 있으므로 스레드 A는 동기화 된 블록으로 들어갑니다.
3. 스레드 a executes instance = new Singleton (); 인스턴스 변수 인스턴스를 비어 있지 않게 설정합니다. (생성자를 호출하기 전입니다.)
4. a exits를 실시하고 스레드 b가 들어갑니다.
5. 스레드 B는 인스턴스가 비어 있는지 확인하고 현재 비어 있지 않습니다 (세 번째 단계에서는 스레드 A에 의해 비어 있지 않도록 설정). 스레드 B는 인스턴스에 대한 참조를 반환합니다. (문제가 발생합니다. 현재, 인스턴스에 대한 참조는 생성자가 호출되지 않기 때문에 싱글 톤 인스턴스가 아닙니다.)
6. 스레드 B가 종료되고 스레드 A가 들어갑니다.
7. 스레드 A는 생성자 메소드를 계속 호출하고 인스턴스의 초기화를 완료하고 반환합니다.
좋은 방법이 없습니까? 좋은 방법이 있어야합니다. 계속 탐구합시다!
// 변정되지 않은 쓰기 게으른 공개 클래스 싱글 톤 문제 해결 {// 싱글 톤 인스턴스 변수 비공개 정적 싱글 톤 인스턴스 = null; // 생성자 개인 싱글 톤 () {} // 싱글 톤 인스턴스 가져 오기 공개 정적 싱글 턴 getInstance () {if (instance == null) {synchronized (Singleton.Class) {// 1 Singleton temp = instance; // 2 if (temp == null) {synchronized (singleton.class) {// 3 temp = new Singleton (); // 4} 인스턴스 = 온도; // 5}}}} system.out.println ( "게으른 싱글 톤의 변형되지 않은 글을 해결하고 있습니다!"); 반환 인스턴스; }} 1. a.
2. 인스턴스가 비어 있으므로 스레드 A는 위치에서 첫 번째 동기화 된 블록으로 들어갑니다 // 1.
3. 스레드 A는 위치 // 2에서 코드를 실행하고 인스턴스를 로컬 변수 온도에 할당합니다. 인스턴스가 비어 있으므로 온도도 비어 있습니다.
4. 온도가 비어 있기 때문에 스레드 A는 위치 // 3의 두 번째 동기화 된 블록으로 들어갑니다. (이 자물쇠가 약간 중복되었다고 생각했습니다)
5. 스레드 a는 위치 // 4에서 코드를 실행하여 온도를 비어 있지 않게 설정하지만 생성자는 아직 호출되지 않았습니다! ( "불완전한 글쓰기"문제)
6. 스레드 A가 블록을 사용하면 스레드 B가 getInstance () 메소드로 들어갑니다.
7. 인스턴스가 비어 있기 때문에 스레드 B는 첫 번째 동기화 된 블록에 들어가려고합니다. 그러나 스레드 A는 이미 내부에 있기 때문입니다. 따라서 입력하는 것은 불가능합니다. 스레드 B 블록.
8. 스레드 A가 활성화되어 위치에서 코드를 계속 실행합니다. 생성자를 호출하십시오. 인스턴스를 생성합니다.
9. 임시 인스턴스 참조를 인스턴스에 할당하십시오. 두 개의 동기화 된 블록을 종료하십시오. 인스턴스를 반환합니다.
10. 스레드 B가 활성화되어 첫 번째 동기화 된 블록으로 들어갑니다.
11. 스레드 B는 위치 // 2에서 코드를 실행하고 인스턴스 인스턴스를 임시 로컬 변수에 할당합니다.
12. 스레드 B는 로컬 변수 온도가 비어 있지 않다고 판단하므로 if 블록을 건너 뜁니다. 인스턴스 인스턴스를 반환합니다.
지금까지 우리는 위의 문제를 해결했지만 갑자기 실 안전 문제를 해결하기 위해 신체에 많은 원사가 감겨있는 것처럼 느껴지므로 지저분하므로 간소화해야합니다.
// Hungry Public Class Singleton {// Singleton 변수, 정적, 스레드 안전 개인 정적 싱글 톤 인스턴스를 보장하기 위해 클래스가로드되면 한 번 초기화됩니다. // 비공개 구조 방법은 생성자를 통해 외부 클래스를 인스턴스화 할 수 없는지 확인합니다. private singleton () {} // 싱글 톤 객체 인스턴스를 가져옵니다. public static singleton getinstance () {System.out.println ( "나는 배고픈 싱글 톤입니다!"); 반환 인스턴스; }}위의 코드를 보았을 때, 나는 세상이 조용하다고 즉시 느꼈습니다. 그러나이 방법은 배고픈 남자 스타일 방법을 채택하며, 이는 싱글 톤 사전 객체를 사전 해제하는 것입니다. 이것의 한 가지 단점은 다음과 같습니다. 건설의 싱글 톤이 크고 건설이 완료된 후에 사용되지 않으면 자원 낭비로 이어질 것입니다.
완벽한 방법이 있습니까? 계속 지켜봐 :
// 내부 클래스는 게으른 공개 클래스 싱글 톤 {개인 정적 클래스 싱글 톤 홀더 {// 싱글 톤 변수 개인 정적 싱글 톤 인스턴스 = 새로운 싱글 톤 (); } // 비공개 구조 방법은 생성자를 통해 외부 클래스를 인스턴스화 할 수 없는지 확인합니다. private singleton () {} // 싱글 톤 객체 인스턴스를 얻습니다 public static singleton getinstance () {System.out.println ( "나는 내부 클래스 싱글 톤입니다!"); 싱글 톤 홀더를 반환합니다. }}게으른 (위의 자원 폐기물을 피하기), 스레드-안전 및 간단한 코드. Java 메커니즘은 내부 클래스 싱글 톤 홀더가 GetInstance () 메소드가 처음으로 요구 될 때만 (게으른 구현)로드되고 로딩 프로세스는 스레드-안전 (스레드 -SAFE 구현)입니다. 내부 클래스가로드 될 때 인스턴스가 인스턴스화됩니다.
위에서 언급 한 비정규적인 글에 대해 간단히 이야기 해 봅시다. 이것이 JVM의 특징입니다. 예를 들어 두 변수를 선언하는 문자열 a; 문자열 B; JVM은 첫 번째 또는 b를로드 할 수 있습니다. 마찬가지로, 인스턴스 = 새로운 싱글 톤 (); 싱글 톤의 생성자를 호출하기 전에 인스턴스를 비 인식으로 설정할 수 있습니다. 이것은 싱글 톤의 대상이 인스턴스화되지 않았다고 말하는 많은 사람들에게 의문입니다. 그래서 어떻게 인스턴스가 비어 있지 않았습니까? 지금 그 가치는 무엇입니까? 이 문제를 이해하려면 문장 인스턴스 = new Singleton (); 실행됩니다. 다음은 귀하에게 설명하기위한 의사 코드입니다.
mem = 할당 (); // 싱글 톤 객체에 메모리를 할당합니다. 인스턴스 = mem; // 인스턴스는 지금 비어 있지 않지만 아직 초기화되지 않았습니다. Ctorsingleton (인스턴스); // 싱글 톤의 생성자를 호출하고 인스턴스를 패스하십시오.
스레드가 인스턴스 = mem;을 실행할 때 인스턴스가 비어 있지 않다는 것을 알 수 있습니다. 다른 스레드가 프로그램에 들어가서 인스턴스 인스턴스가 비어 있지 않으면 인스턴스를 반환합니다. 그리고 현재 싱글 톤의 생성자는 인스턴스를 호출하지 않았으며 현재 값은 allocate ()에 의해 리턴 된 메모리 객체입니다. 따라서 두 번째 스레드는 싱글 톤 객체가 아니라 메모리 객체가됩니다.
위는 싱글 톤 모델에 대한 나의 작은 생각과 이해입니다. 나는 모든 위대한 신들을 따뜻하게 환영하며 와서 인도하고 비판합니다.