1. 소개

이 사용방법(예제)에서는 이중 확인 잠금 디자인 패턴에 대해 설명합니다. 이 패턴은 잠금 조건을 미리 확인하여 잠금 획득 횟수를 줄입니다. 그 결과 일반적으로 성능이 향상됩니다. 그러나 double-checked locking은 Java 5 이전에 깨졌다는 점에 유의해야 합니다 .

어떻게 작동하는지 자세히 살펴보겠습니다.

2. 시행

먼저 엄격한 동기화를 사용하는 간단한 싱글톤을 고려해 보겠습니다.

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

    // private constructor and other methods ...
}

이 클래스가 스레드로부터 안전함에도 불구하고 분명한 성능 단점이 있음을 알 수 있습니다. 싱글톤의 인스턴스를 얻고자 할 때마다 잠재적으로 불필요한 잠금을 획득해야 합니다.

이 문제를 해결하기 위해 먼저 객체를 생성해야 하는지 확인하고 이 경우에만 잠금을 획득할 수 있는지 확인하는 것으로 시작할 수 있습니다.

더 나아가 작업을 원자성으로 유지하기 위해 동기화된 블록에 들어가자마자 동일한 검사를 다시 수행하려고 합니다.

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

    // private constructor and other methods...
}

이 패턴에서 염두에 두어야 할 한 가지는 캐시 불일치 문제를 방지하기 위해 필드가  휘발성 이어야 한다는 것입니다 . 실제로 Java 메모리 모델은 부분적으로 초기화된 개체의 게시를 허용하며 이로 인해 미묘한 버그가 발생할 수 있습니다.

3. 대안

이중 확인 잠금이 잠재적으로 속도를 높일 수 있지만 적어도 두 가지 문제가 있습니다.

  • 제대로 작동하려면 volatile 키워드가 필요하므로 Java 1.4 이하 버전과 호환되지 않습니다.
  • 매우 장황하고 코드를 읽기 어렵게 만듭니다.

이러한 이유로 이러한 결함이 없는 다른 옵션을 살펴보겠습니다. 다음 메서드는 모두 동기화 작업을 JVM에 Delegation합니다.

3.1. 초기 초기화

스레드 안전성을 달성하는 가장 쉬운 방법은 개체 생성을 인라인하거나 동등한 정적 블록을 사용하는 것입니다. 이는 정적 필드와 블록이 차례로 초기화된다는 사실을 이용합니다( Java 언어 사양 12.4.2 ).

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }
    
     // private constructor and other methods...
}

3.2. 요청 시 초기화

또한 이전 단락의 Java 언어 사양 참조에서 메서드 또는 필드 중 하나를 처음 사용할 때 클래스 초기화가 발생한다는 것을 알고 있으므로 중첩된 정적 클래스를 사용하여 지연 초기화를 구현할 수 있습니다.

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

     // private constructor and other methods...
}

이 경우 InstanceHolder 클래스는 getInstance를 호출하여 처음 액세스할 때 필드를 할당합니다 .

3.3. Enum 싱글톤

마지막 솔루션은 Joshua Block의 Effective Java 책(항목 3)에서 제공되며 class 대신 enum을 사용합니다 . 이 글을 쓰는 시점에서 이것은 싱글톤을 작성하는 가장 간결하고 안전한 방법으로 간주됩니다.

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. 결론

요약하자면, 이 빠른 기사는 이중 확인 잠금 패턴, 그 한계 및 몇 가지 대안을 살펴보았습니다.

실제로 과도한 장황함과 이전 버전과의 호환성 부족으로 인해 이 패턴은 오류가 발생하기 쉬우므로 피해야 합니다. 대신 JVM이 동기화를 수행하도록 하는 대안을 사용해야 합니다.

항상 그렇듯이 모든 예제의 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner