문제

"실제로 Java 동시성"을 읽고 섹션 3.5에는이 부분이 있습니다.

public Holder holder;
public void initialize() {
     holder = new Holder(42);
}

두 가지 인스턴스를 만드는 명백한 실 안전 위험 외에 Holder,이 책은 가능한 출판 문제가 발생할 수 있다고 주장합니다.

또한, a Holder 다음과 같은 클래스

public Holder {
    int n;
    public Holder(int n) { this.n = n };
    public void assertSanity() {
        if(n != n)
             throw new AssertionError("This statement is false.");
    }
}

an AssertionError 던질 수 있습니다!

이것이 어떻게 가능한지? 내가 생각할 수있는 유일한 방법은 그러한 말도 안되는 행동을 허용하는 것입니다. Holder 생성자는 차단되지 않으므로 생성자 코드는 여전히 다른 스레드에서 실행되는 동안 인스턴스에 대한 참조가 생성됩니다.

이게 가능해?

도움이 되었습니까?

해결책

이것이 가능한 이유는 Java에 메모리 모델이 약하기 때문입니다. 읽기와 쓰기의 순서를 보장하지는 않습니다.

이 특정 문제는 두 개의 스레드를 나타내는 다음 두 개의 코드 스 니펫으로 재현 할 수 있습니다.

스레드 1 :

someStaticVariable = new Holder(42);

스레드 2 :

someStaticVariable.assertSanity(); // can throw

표면적으로 이것이 일어날 수있는 것은 불가능한 것 같습니다. 이런 일이 발생할 수있는 이유를 이해하려면 Java 구문을지나 훨씬 낮은 수준으로 내려야합니다. 스레드 1에 대한 코드를 보면 본질적으로 일련의 메모리 쓰기 및 할당으로 나눌 수 있습니다.

  1. pointer1에 대한 할당 메모리
  2. 오프셋 0에서 42에 pointer1에 42를 씁니다
  3. somestaticvariable에 pointer1을 씁니다

Java는 메모리 모델이 약하기 때문에 스레드 2의 관점에서 다음 순서로 코드가 실제로 실행할 수 있습니다.

  1. pointer1에 대한 할당 메모리
  2. somestaticvariable에 pointer1을 씁니다
  3. 오프셋 0에서 42에 pointer1에 42를 씁니다

무서운? 예,하지만 일어날 수 있습니다.

그러나 이것이 의미하는 바는 스레드 2가 이제 호출 할 수 있다는 것입니다. assertSanity ~ 전에 n 값을 얻었습니다 42. 값이 가능합니다. n 두 번 읽어야합니다 assertSanity, 작동 전 #3이 끝나기 전에 한 번 완료되고 한 번은 두 가지 다른 값을보고 예외를 던집니다.

편집하다

Jon Skeet에 따르면,, AssertionError Migh는 여전히 발생합니다 Java 8 필드가 최종적이지 않는 한.

다른 팁

Java 메모리 모델 사용된 에 대한 과제가되기 위해 Holder 객체 내의 변수에 할당되기 전에 참조가 표시 될 수 있습니다.

그러나 Java 5의 최신 메모리 모델은 최소한 최종 필드의 경우 불가능합니다. 생성자 내의 모든 할당은 "이전"이라는 이름의 모든 할당이 변수에 대한 새로운 객체에 대한 할당을 할당합니다. 참조 Java 언어 사양 섹션 17.4 자세한 내용은 다음과 같습니다. 가장 관련성있는 스 니펫은 다음과 같습니다.

객체는 생성자가 완료되면 완전히 초기화되는 것으로 간주됩니다. 해당 객체가 완전히 초기화 된 후 객체에 대한 참조 만 볼 수있는 스레드는 해당 객체의 최종 필드에 대한 올바르게 초기화 된 값을 볼 수 있습니다.

따라서 예제는 여전히 실패 할 수 있습니다 n 비정규이지만, 당신이 만들면 괜찮을 것입니다. n 결정적인.

물론 :

if (n != n)

JIT 컴파일러가이를 최적화하지 않는다고 가정 할 때, 비 결합 변수에 대해서는 확실히 실패 할 수 있습니다.

  • 페치 LHS : n
  • 페치 RHS : n
  • LHS와 RHS를 비교하십시오

그러면 두 페치 사이에서 값이 변경 될 수 있습니다.

글쎄,이 책에서 그것은 첫 번째 코드 블록을 말합니다.

여기서 문제는 보유자 클래스 자체가 아니라 홀더가 제대로 게시되지 않았다는 것입니다. 그러나, 보유자는 N 필드를 최종으로 선언함으로써 부적절한 출판물에 면역 될 수 있으며, 이는 홀더를 불변으로 만들 수 있습니다. 섹션 3.5.2를 참조하십시오

그리고 두 번째 코드 블록의 경우 :

동기화는 홀더를 다른 스레드로 보이게하는 데 사용되지 않았기 때문에 홀더가 제대로 게시되지 않았다고 말합니다. 부적절하게 출판 된 물건으로 두 가지가 잘못 될 수 있습니다. 다른 스레드는 홀더 필드에 대한 오래된 값을 볼 수 있으므로 값이 홀더에 배치 되었음에도 불구하고 Null Reference 또는 기타 이전 값을 볼 수 있습니다. 그러나 훨씬 더 나쁜 것은 다른 스레드는 홀더 참조에 대한 위조 값을 볼 수 있지만 홀더의 상태에 대한 값을 오래 볼 수 있습니다. [16] 사물을 덜 예측하기 쉽게 만들려면 스레드는 처음으로 필드를 읽은 다음 다음에 더 최신 가치를 볼 수 있으므로 Assertsanity가 AssertionError를 던질 수있는 이유입니다.

나는 Jaredpar가 그의 의견에서 이것을 명시 적으로 만들었다고 생각합니다.

(참고 : 여기에서 투표를 찾지 않음 - 답변은 의견보다 더 자세한 정보를 허용합니다.)

기본 문제는 적절한 동기화가 없으면 메모리에 쓰는 방법이 다른 스레드에서 나타날 수 있다는 것입니다. 전형적인 예 :

a = 1;
b = 2;

하나의 스레드에서이를 수행하면 A가 1으로 설정되기 전에 B가 2로 설정된 것을 볼 수 있습니다. 또한, 그 변수 중 하나가 업데이트되고 다른 변수가 업데이트됩니다.

제정신 관점에서 이것을보고, 당신이 진술을한다고 가정하면

if(n != n)

원자체 (합리적이라고 생각하지만 확실하지 않음). 그러면 어설 션 예외는 결코 던져 질 수 없었습니다.

이 예제는 "최종 필드를 포함하는 객체에 대한 참조는 생성자를 피하지 않았다"고합니다.

새 연산자와 새 홀더 객체를 인스턴스화하면

  1. Java Virtual Machine은 먼저 힙에 충분한 공간을 할당하여 홀더 및 슈퍼 클래스에 선언 된 모든 인스턴스 변수를 유지할 수 있습니다.
  2. 둘째, 가상 머신은 모든 인스턴스 변수를 기본 초기 값으로 초기화합니다. 3.C 셋째, 가상 머신은 홀더 클래스에서 메소드를 호출합니다.

위에서 참조하십시오 : http://www.artima.com/designtechniques/initializationp.html

가정 : 1st 스레드가 오전 10시를 시작하고, 새로운 홀러의 호출 (42), 1) Java Virtual Machine은 먼저 모든 인스턴스를 보유 할 수있는 충분한 공간을 할당함으로써 홀더 객체를 불러 냈습니다. 변수는 홀더와 슈퍼 클래스로 선언되었습니다. - 10:01 시간 2) 둘째, 가상 머신은 모든 인스턴스 변수를 기본 초기 값으로 초기화합니다. 10:02 시간 3) 셋째, 가상 머신은 홀더 클래스에서 메소드를 호출합니다. .-- 10:04 시간이 시작됩니다

이제 Thread2는 -> 10:02:01에서 시작했으며, 호출 AssertsAnity () 10:03으로, 그때까지 N은 기본값으로 초기화되었으며, 두 번째 스레드는 오래된 데이터를 읽습니다.

// 안전하지 않은 출판물 공공 보유자;

공개 최종 보유자 가이 문제를 해결하는 경우

또는

개인 int n; 개인 최종 INT N을 만드는 경우; 이 문제를 재조정합니다.

참조하시기 바랍니다 : http://www.cs.umd.edu/~pugh/java/memorymodel/jsr-133-faq.html 최종 필드는 새로운 JMM에서 어떻게 작동합니까?

나는 또한 그 예에 매우 당황했다. 주제를 철저히 설명하는 웹 사이트를 찾았으며 독자가 유용 할 수 있습니다.https://www.securecoding.cert.org/confluence/display/java/tsm03-j.+do+ not +partical+ initialized+objects

편집 : 링크의 관련 텍스트는 다음과 같습니다.

JMM은 컴파일러가 새 헬퍼 객체에 대한 메모리를 할당하고 새 헬퍼 객체를 초기화하기 전에 해당 메모리에 대한 참조를 할당 할 수 있도록 허용합니다. 다시 말해, 컴파일러는 쓰기를 도우미 인스턴스 필드로 재정렬 할 수 있고 전자가 먼저 발생하도록 도우미 객체 (즉, this.n = n)를 초기화하는 쓰기를 재정렬 할 수 있습니다. 이것은 다른 스레드가 부분적으로 초기화 된 헬퍼 객체 인스턴스를 관찰 할 수있는 레이스 창을 노출시킬 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top