2

Wikipedia에 나와있는 자바 싱글 톤 구현 중 하나가 :Java에서 Double Checking Locking Singleton을 사용하는 것이 안전합니까?

public class SingletonDemo { 
    private static volatile SingletonDemo instance = null; 

    private SingletonDemo() { 
    } 

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

객체가 완전히 때 생성자 완료를 초기화 할 것으로 간주됩니다

한다는 Java Language Specification 17, paragraph 5있다. 객체가 완전히 초기화 된 후에 객체에 대한 참조 만 볼 수있는 스레드는 해당 객체의 최종 필드에 대해 올바르게 초기화 된 값을 볼 수 있습니다.

좋아, 그럼 우리의 SingletonDemo 클래스가 아닌 최종 필드가 있다고 상상해보십시오. 따라서 동시 스레드는 생성자에 지정된 올바른 값 대신 기본값을 읽을 수 있습니까?

+1

"이중 검사 잠금을 사용하지 마십시오"라는 이중 검사 잠금에 대한 대답이 아닙니까? 작동하지 않거나 도움이되지 않습니다. – user2357112

+1

@ user2357112 사실이지만 흥미로운 질문입니다. 어쨌든 대답해야한다고 생각합니다. – Navin

+0

아니요, DCL은 종종 실제로 유용합니다. 단지주의해야합니다. 어쨌든 많은 소스에 나열된 일반적인 예입니다.그래서 저는 그것이 정말로 안전한지를 결정하기를 원합니다. Singleton 또는 DCL에 관한 것이 아니라 Java 메모리 모델 및 스레딩 모델에 관한 것입니다. –

답변

5

Java 5 이상에서는 double-checked locking (DCL)을 올바르게 구현할 수 있습니다. Java 4 및 이전 버전에서는 동기화와 관련하여 volatile의 동작이 올바르게 지정되지 않았기 때문에 가능하지 않았습니다. 실제로는 부적절했습니다.

Java 5 JRE 이상을 사용하여 실행할 때 질문에 포함 된 코드는 DCL ...에 대한 올바른 구현입니다.

하지만 (IMO), DCL을 사용할 가치가 없습니다. 특히 당신 (또는 당신을 후발 개발자) 정확히/안전하게 안전하게 수행하는 방법 완전히 이해하지 않습니다.

현실적인 Java 응용 프로그램에서 성능 이점이 너무 적어서 가치있는 최적화가되지 못합니다. (이 경우, 당신은 아마/남용 싱글을 오용 ... 그리고 그 다른 방법으로 당신을 물린 것이다!된다)


좋아, 그래서 우리 SingletonDemo 클래스는 final이 아닌 필드가 상상. 따라서 동시 스레드는 생성자에 지정된 올바른 값 대신 기본값을 읽을 수 있습니까?

는 (인용 된 JLS 텍스트 그것은 여기에 관련이 없습니다. 그것은 약 final 필드입니다. 완전히 다른 상황에 관한 것입니다. 그리고 당신은 final 필드의 동작이없는에서 동기화 비 final 필드의 행동을 추론 할 수 동기화).

귀하의 질문에 대한 대답은 아니오입니다. 귀하의 질문에있는 코드는 동시 스레드가 기본값을 보지 못하도록하기 위해 충분한 조치를 취하고 있습니다. 이유를 이해하기 위해 읽고 다음

  • JLS 제 17.4의 모든 및
  • 내가 기억하는 경우 (DCL에 대한 섹션을 포함 게츠 등 "연습 자바 동시성"의 마지막 장 정확하게 ...)
0

동시 스레드는 초기화되지 않은 상태에서도 객체의 최종 필드를 볼 수 있습니다. 코드에서 휘발성 코드를 제거하면 문제가 발생할 수 있습니다. 동기화가없는 경우 객체를 생성하는 스레드 만이 구조체 초기화가 완료된 후에 객체에 대한 참조가 반환된다는 것을 보장합니다.

+0

그래,하지만 그것은 명백하다. 비 휘발성 변수에 대한 읽기/쓰기는 happen-before에 의해 바인딩되지 않으므로 질문은 이것에 관한 것이 아닙니다. –

4

견적은 말한다 : 최종 필드와 생성자가 완료

경우 스레드가 초기화 값을 볼 수 있습니다.

그것은 하지 말 않습니다

것은 아닌 최종 필드 THEN 스레드가 초기화 된 값을 볼 수없는 경우.

그 예에서도 휘발성이 보장되는 안전한 의미의 의미입니다.

또한 DCL이 매우 유용하다고 말합니다. 거의 모든 상황에서 복잡하고 오류가 발생하기 쉬운 구조를 사용하지 않아도되는 더 나은 방법이 있다고 말하고 싶습니다. 우선 순위에 따라 :

  • 이 위키 링크를 인용 수요 홀더 관용구에
0

를 초기화를 사용하여 열거

  • 을 사용하는 모든
  • 에서 싱글 톤을 사용하지 않는,이 것

    // Broken under Java 1.4 and earlier semantics for volatile 
    class Foo { 
        private volatile Helper helper; 
        public Helper getHelper() { 
         Helper result = helper; 
         if (result == null) { 
          synchronized(this) { 
           result = helper; 
           if (result == null) { 
            helper = result = new Helper(); 
           } 
          } 
         } 
         return result; 
        } 
    } 
    

    "휘발성 필드는 한 번만 액세스되기 때문에" 방법의 전반적인 성능을 최대 25 % 향상시킬 수 있습니다. "

    또는 당신이 사용할 수있는 Initialization-on-demand holder idiom :

    동안 클래스가 실제로 액세스 될 때까지 LazyHolder INSTANCEgetInstance() 방법 중 인 초기화되지 않은 클래스 로딩 때문에 작동
    public class Something { 
        private Something() {} 
    
        private static class LazyHolder { 
         private static final Something INSTANCE = new Something(); 
        } 
    
        public static Something getInstance() { 
         return LazyHolder.INSTANCE; 
        } 
    } 
    

    .

    하거나 특별한 이유 과도하게 복잡 초기화를 그만 표준 열망 초기화 사용 초기화 지연이 실제로 도움이 매우 드문 경우가있다

    static final Singleton INSTANCE = new Singleton(); 
    

    합니다. 그리고 만약 여러분의 싱글 톤이 초기화 비용이 높고 정상적인 프로그램 실행 중에 있다면 - 에 전혀 쓰지 않을 것 같습니다.. 그러나이 경우는 일반적으로 아키텍처 문제이며 지연 초기화는 빠른 & 더티 픽스입니다.

    그래서 당신은 응용 프로그램을 프로파일 링하고이 강력한 성능에 부정적인 문제로 결정했다 않는 열망 초기화 를 사용해야합니다. 그러나 게으른 초기화를 사용하는 대신에 아키텍처 레벨에서 이것을 해결하는 다른 많은 방법이 있습니다.

  • 관련 문제