자바 언어 단일 모드 의 실현

5921 단어
단일 모드 는 디자인 모드 에서 생 성 된 모드 의 하나 입 니 다.창설 형 모드 는 말 그대로 대상 의 창설 과정 을 통제 하 는 데 쓰 이 잖 아 요.그럼 단일 모드 로 제어 되 는 대상 의 생 성 과정 은 어떤 특징 이 있 습 니까?그 이름 의 단일 두 글 자 를 통 해 우 리 는 이러한 모델 을 통 해 특정한 class 가 전체 가상 컴퓨터 에서 하나의 인 스 턴 스 만 있 을 수 있다 는 것 을 추측 하기 어렵 지 않다.
첫 번 째, 기본 적 인 지연 생 성 방법
우 리 는 먼저 가장 간단 한 실현 방식 을 살 펴 보 자.
package com.example.hello;
public class Singleton {
    private static Singleton instance;
    
    private Singleton() { }
    
    public static Singleton getInstance () {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    public void sayHelloToWorld () {
        System.out.print("Hello, world");
    }
}

이러한 실현 방식 에서 구조 함 수 는 private 로 밝 혀 졌 기 때문에 Singleton 류 의 대상 을 방문 하려 면 Singleton. getInstance () 를 통 해 만 이 루어 질 수 있 습 니 다. Singleton. getInstance () 를 통 해 대상 의 생 성 과정 을 제어 하면 Singleton 류 의 대상 은 한 번 만 만 만 들 수 있 습 니 다.정말 잘 이 루어 진 것 같 습 니 다.그런데 사실은?실제로 병발 환경 에서 이런 실현 방식 은 매우 취약 해 보인다.
이런 실현 방식 은 최종 조건 이 발생 할 것 이다.일반적으로 더 밑 에 있 는 관점 에서 볼 때 변 수 를 수정 하 는 과정 은 대체적으로 몇 가지 절차 로 구성 되 고 메모리 에서 변 수 를 읽 고 레지스터 까지 변 수 를 수정 한 다음 에 변 수 를 메모리 에 다시 쓴다 고 볼 수 있다.이 몇 단계 의 모든 두 단계 사이 에 실행 중인 스 레 드 는 시간 이 다 되 어 실행 을 종료 할 수 있 습 니 다.
이 케이스 의 경우 정적 은 인 스 턴 스 검사, 대상 생 성, 할당 과정 에서 발생 합 니 다.첫 번 째 스 레 드 ThreadA 가 Singleton. getInstance () 를 먼저 호출 한다 고 가정 하면 인 스 턴 스 가 null 인 것 을 발견 하고 대상 을 만 든 다음 에 인 스 턴 스 에 값 을 부여 합 니 다. 대상 을 만 들 거나 할당 이 완료 되 기 전 어느 순간 에 시간 영화 가 다 써 서 실행 을 종료 합 니 다.이때 다른 스 레 드 ThreadB 는 Singleton. getInstance () 를 호출 했 습 니 다. 인 스 턴 스 를 null 로 발견 하여 대상 이 VM 에서 유일 성 을 보장 할 수 없습니다.
두 번 째, 동기 화 지연 생 성 방법
package com.example.hello;
public class Singleton {
    private static Singleton instance;
    
    private Singleton() { }
    
    public static synchronized Singleton getInstance () {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    public synchronized void sayHelloToWorld () {
        System.out.print("Hello, world");
    }
}

이 방법 은 이전 방법 에 비해 기본적으로 각 방법 을 synchronized 로 만 설명 하 는 것 이다.그러나 이 작은 변경 은 스 레 드 안전성 을 잘 보장 할 수 있다.싱글 턴 getInstance () 는 대부분의 경우 잠 금 을 추가 할 필요 가 없 으 며, 직접 방법 을 synchronized 라 고 밝 히 면 잠 금 을 추가 하 는 비용 이 좀 든다 고 한다.그러나 자 물 쇠 를 추가 하 는 것 은 경쟁 에 자 물 쇠 를 추가 하 는 것 과 비 경쟁 에 자 물 쇠 를 추가 하 는 것 으로 나 눌 수 있다. 바로 어느 순간 에 여러 개의 스 레 드 가 동시에 자 물 쇠 를 빼 앗 는 것 이지 경쟁 에 자 물 쇠 를 추가 하 는 것 이 아니 라 어느 순간 에 하나의 스 레 드 만 그 자 물 쇠 를 빼 앗 는 것 이다.골동품 을 제외 하고 현대 의 JVM 에서 대부분 상황 에서 나타 날 비 경쟁 자 물 쇠 는 매우 빠 를 것 이다. 대략 몇 개 또는 10 여 개의 시계 주기 만 있 을 뿐 이 므 로 이곳 에서 의 이런 자 물 쇠 를 추가 하 는 비용 을 걱정 할 필요 가 거의 없다.
이와 함께 이런 방법 도 자바 와 함께 큰 소 가 추천 하 는 방법 이다.
세 번 째, 이중 검사 잠 금 방법
package com.example.hello;
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    public synchronized void sayHelloToWorld() {
        System.out.print("Hello, world");
    }
}

이런 방법 은 앞의 방법 에 비해 자 물 쇠 를 넣 는 비용 을 조금 낮 출 수 있다.그러나 인 스 턴 스 는 volatile 로 밝 혀 졌 음 을 주의해 야 한다.volatile 이 라 고 밝 히 지 않 으 면, 가시 적 인 문제 가 생 길 수 있다.JVM 이 제공 하 는 보증 은 그 코드 가 단일 작업 환경 에서 다 중 작업 환경 에서 실 행 됩 니 다. 최종 결 과 는 똑 같 습 니 다. JVM 이 무질서 한 실행 과 같은 최적화 조 치 를 취 할 지 여 부 를 상관 하지 않 습 니 다.따라서 할당 문 인 스 턴 스 = new Singleton () 은 좀 더 미시적 인 측면 에서 볼 때 대상 이 완전히 생 성 되 지 않 았 을 때 인 스 턴 스 가 대상 의 메모리 블록 을 가리 킬 수 있 습 니 다.따라서 다른 스 레 드 는 아직 완전히 만들어 지지 않 은 대상 을 볼 수 있 습 니 다.인 스 턴 스 를 volatile 로 성명 하면 이런 문 제 를 피 할 수 있다.
그러나 이런 모델 은 실현 되 기 시작 하면 좀 복잡 해 야 한다.
네 번 째, 지연 없 이 만 드 는 방법 (미리 초기 화)
package com.example.hello;
public class Singleton {
    private static Singleton instance = new Singleton();;
    
    private Singleton() { }
    
    public static Singleton getInstance () {
        return instance;
    }
    
    public synchronized void sayHelloToWorld () {
        System.out.print("Hello, world");
    }
}

다 중 스 레 드 문 제 를 피 할 수 있 는 또 다른 방법이러한 방법 은 classloader 의 메커니즘 을 빌려 정적 으로 만 드 는 방법 을 통 해 class 가 load 되 었 을 때 대상 을 만 들 고 뒤에서 Singleton. getInstance () 를 호출 할 때 잠 금 비용 이 필요 하지 않 습 니 다.그러나 이런 방법의 단점 도 분명 하 다.class 는 여러 가지 이유 로 load 될 수 있 지만 인 스 턴 스 는 Singleton. getInstance () 를 호출 한 후에 만 만 만 들 필요 가 있 습 니 다.그런 큰 싱글 톤 대상 에 대해 서 는 불필요 한 메모리, 프로세서 시간 등 자원 을 너무 많이 소모 할 수 있 습 니 다.
다섯 번 째, 지연 없 이 만 드 는 방법 2
package com.example.hello;
public class Singleton {
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
    public synchronized void sayHelloToWorld() {
        System.out.print("Hello, world");
    }
}

이런 방법 은 앞의 네 번 째 방법 과 똑같다 고 할 수 있다.
여섯 번 째, 정적 내부 클래스 방법
package com.example.hello;
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    public synchronized void sayHelloToWorld() {
        System.out.print("Hello, world");
    }
}

이 방법 은 앞의 초기 화 기술 과 JVM 의 지연 로드 체 제 를 결합 시 켜 흔히 볼 수 있 는 코드 경로 에 자 물 쇠 를 추가 하지 않 아 도 되 고 로드 를 지연 시 킬 수 있 는 기술 이다.그것 은 대상 을 초기 화 하기 위해 서 전문 적 인 Singleton Holder 를 사용 합 니 다.JVM 은 이러한 종류의 초기 화 작업 을 지연 시 키 고 이 종 류 를 사용 하기 시작 할 때 까지 초기 화 합 니 다. 또한 정적 초기 화 를 통 해 Singleton 을 초기 화 하기 때문에 추가 동기 화 를 필요 로 하지 않 습 니 다.모든 스 레 드 가 getInstance () 를 처음 호출 할 때 Singleton Holder 를 불 러 오고 초기 화 합 니 다. 이 때 정적 초기 화 기 는 Singleton 의 초기 화 를 실행 합 니 다.
Done。

좋은 웹페이지 즐겨찾기