Programing

이중 체크 잠금에서 휘발성이 사용되는 이유

lottogame 2020. 11. 12. 07:43
반응형

이중 체크 잠금에서 휘발성이 사용되는 이유


에서 헤드 퍼스트 디자인 패턴 책을 두 번 확인 잠금과 싱글 톤 패턴은 아래와 같이 구현되었습니다 :

public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile사용되고 있는지 이해가 안 돼요 . volatile사용이 이중 확인 잠금, 즉 성능을 사용하는 목적을 무효화 하지 않습니까?


volatile필요한 이유를 이해하기위한 좋은 자료 JCIP에서 나옵니다 . Wikipedia에는 그 자료에 대한 적절한 설명 도 있습니다.

실제 문제는 구성이 완료되기 전에 Thread A메모리 공간을 할당 할 수 있다는 instance것입니다 instance. Thread B그 과제를보고 그것을 사용하려고 할 것입니다. 이로 Thread B인해 부분적으로 구성된 버전의 instance.


@irreputable이 인용했듯이 휘발성은 비싸지 않습니다. 비용이 많이 들더라도 성능보다 일관성이 우선되어야합니다.

Lazy Singleton을위한 또 하나의 깔끔하고 우아한 방법이 있습니다.

public final class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

소스 기사 : Wikipedia의 Initialization-on-demand_holder_idiom

소프트웨어 엔지니어링에서 Initialization on Demand Holder (디자인 패턴) 관용구는 지연로드 된 싱글 톤입니다. 모든 버전의 Java에서이 관용구는 우수한 성능으로 안전하고 동시적인 지연 초기화를 가능하게합니다.

클래스에는 static초기화 변수 가 없기 때문에 초기화는 간단하게 완료됩니다.

그 안의 정적 클래스 정의 LazyHolder는 JVM이 LazyHolder를 실행해야한다고 결정할 때까지 초기화되지 않습니다.

정적 클래스 LazyHolder는 정적 메서드 getInstance가 Singleton 클래스에서 호출 될 때만 실행 되며 처음 발생하면 JVM이 LazyHolder클래스를 로드하고 초기화합니다 .

이 솔루션은 특수한 언어 구조 (예 : volatile또는 synchronized) 없이 스레드로부터 안전 합니다.


음, 성능을 위해 이중 확인 된 잠금이 없습니다. 그것은이다 깨진 패턴.

옆 감정을 떠나, volatile그것을하지 않고 두 번째 스레드가 통과하는 시간 때문에 여기에 instance == null, 첫 번째 스레드가 구성되지 않을 수도 있습니다 new Singleton()개체의 창조한다는 하나의 약속 : 아직 전에-발생 에 할당 instance임의의 thread하지만 하나 실제로 객체를 생성합니다.

volatile차례로 읽기와 쓰기 사이에 발생 전 관계를 설정 하고 끊어진 패턴을 수정합니다.

성능을 찾고 있다면 홀더 내부 정적 클래스를 대신 사용하십시오.


만약 당신이 그것을 가지고 있지 않다면, 두 번째 쓰레드는 첫 번째 그것을 null로 설정 한 후에 동기화 된 블록에 들어갈 수 있고, 당신의 로컬 캐시는 여전히 그것이 null이라고 생각할 것입니다.

첫 번째는 정확성을위한 것이 아니라 (당신이 맞다면 스스로 패배하는 것이 맞다면) 최적화를위한 것입니다.


휘발성 읽기는 그 자체로 비싸지 않습니다.

getInstance()휘발성 읽기의 영향을 관찰하기 위해 엄격한 루프에서 호출하는 테스트를 설계 할 수 있습니다 . 그러나 그 테스트는 현실적이지 않습니다. 이러한 상황에서 프로그래머는 일반적으로 getInstance()한 번 호출 하고 사용 기간 동안 인스턴스를 캐시합니다.

Another impl is by using a final field (see wikipedia). This requires an additional read, which may become more expensive than the volatile version. The final version may be faster in a tight loop, however that test is moot as previously argued.


Declaring the variable as volatile guarantees that all accesses to it actually read its current value from memory.

Without volatile, the compiler may optimize away the memory accesses and keep its value in a register, so only the first use of the variable reads the actual memory location holding the variable. This is a problem if the variable is modified by another thread between the first and second access; the first thread has only a copy of the first (pre-modified) value, so the second if statement tests a stale copy of the variable's value.

참고URL : https://stackoverflow.com/questions/7855700/why-is-volatile-used-in-double-checked-locking

반응형