최신 값을 읽은 후 오래된 값 읽기 [중복]

Nov 24 2020

이 예를 고려하십시오. 우리는 :

int var = 0;

스레드 A :

System.out.println(var);
System.out.println(var);

스레드 B :

var = 1;

스레드는 동시에 실행됩니다. 다음 출력이 가능합니까?

1
0

즉, 새 값을 읽은 후 원래 값을 읽습니다. 은 var휘발성이 아니다. 내 직감은 불가능하다는 것입니다.

답변

3 Eugene Nov 26 2020 at 11:41

당신은 System.out.println내부적으로 synchronized(this) {...}그것을 사용 하고 있습니다. 그러나 그것으로도, 당신의 독자 스레드는 여전히 관찰 할 수 있습니다 1, 0.

나는 이것에 대한 전문가는 아니지만 Alexey Shipilev의 많은 비디오 / 예제 / 블로그를 살펴본 후 적어도 무언가를 이해한다고 생각합니다.

JLS는 다음과 같이 말합니다.

x와 y가 동일한 스레드의 작업이고 x가 프로그램 순서에서 y 앞에 오면 hb (x, y)입니다.

모두의 읽기 이후 varprogram order, 우리는 그릴 수 있습니다 :

                (po) 
firstRead(var) ------> secondRead(var)
// po == program order

이 문장은 또한 이것이 happens-before주문을 구축한다고 말합니다 .

                (hb) 
firstRead(var) ------> secondRead(var)
// hb == happens before

그러나 그것은 "같은 스레드"내에 있습니다. 다중 스레드에 대해 추론 하려면 동기화 순서 를 조사해야합니다 . 에 대한 동일한 단락이 다음과 happens-before order같이 말하고 있기 때문에 필요합니다 .

액션 x 가 다음 액션 y 와 동기화 되면 hb (x, y)도 있습니다.

따라서 program order와 사이에이 일련의 동작을 구축 synchronizes-with order하면 결과에 대해 추론 할 수 있습니다. 이를 코드에 적용 해 보겠습니다.

            (NO SW)                    (hb)
write(var) ---------> firstRead(var) -------> secondRead(var)

// NO SW == there is "no synchronizes-with order" here
// hb    == happens-before

그리고 이것은 같은 장happens-before consistency 에서 작용하는 부분입니다 .

일련의 작업 A는 A의 모든 읽기 r에 대해 일관되기 전에 발생합니다. 여기서 W (r)는 r에 의해 표시되는 쓰기 작업이고 hb (r, W (r)) 또는 거기에있는 경우가 아닙니다. wv = rv 및 hb (W (r), w) 및 hb (w, r)와 같은 쓰기 w가 A에 존재합니다.

전 발생 일관된 작업 집합에서 각 읽기는 전 발생 순서에 의해 볼 수있는 쓰기를 확인합니다.

나는 첫 번째 문장을 매우 모호하게 이해하고 있으며 Alexey가 말했듯이 이것이 가장 도움이 된 곳입니다.

읽기는에서 발생한 마지막 쓰기 happens-before또는 다른 쓰기를 참조 합니다.

거기가없고 synchronizes-with order묵시적으로가 없기 happens-before order때문에 읽기 스레드는 레이스를 통해 읽을 수 있습니다. 따라서 1,보다 0.


즉시 당신이 올바른 소개로 synchronizes-with order, 여기에서 예를 하나

모니터 m의 잠금 해제 작업은 모든 후속 잠금 작업과 동기화됩니다.

휘발성 변수 v에 대한 쓰기는 모든 스레드에 의한 v의 모든 후속 읽기와 동기화됩니다.

그래프가 변경됩니다 (을 선택했다고 가정 해 보겠습니다 var volatile).

               SW                       PO
write(var) ---------> firstRead(var) -------> secondRead(var)

// SW == there IS "synchronizes-with order" here
// PO == happens-before

PO(프로그램 순서)는 HBJLS 의이 답변에서 인용 한 첫 번째 문장을 통해 (이전에 발생했습니다 ) 제공합니다 . 그리고 SW제공 HB하기 때문에 :

액션 x가 다음 액션 y와 동기화되면 hb (x, y)도 있습니다.

이와 같이 :

               HB                       HB
write(var) ---------> firstRead(var) -------> secondRead(var)

그리고 지금 happens-before order읽기 스레드가 "지난 HB에 쓰여진"된 값을 읽거나 읽는 것을 의미 말한다 1다음 것은 0불가능하다.


예제 jcstress 샘플을 가져 와서 약간의 변경 사항을 소개했습니다 System.out.println.

@JCStressTest
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Doing both reads early.")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "Doing both reads late.")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "Doing first read early, not surprising.")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "First read seen racy value early, and the second one did not.")
@State
public class SO64983578 {

    private final Holder h1 = new Holder();
    private final Holder h2 = h1;

    private static class Holder {

        int a;
        int trap;
    }

    @Actor
    public void actor1() {
        h1.a = 1;
    }

    @Actor
    public void actor2(II_Result r) {
        Holder h1 = this.h1;
        Holder h2 = this.h2;
        
        h1.trap = 0;
        h2.trap = 0;

        synchronized (this) {
            r.r1 = h1.a;
        }

        synchronized (this) {
            r.r2 = h2.a;
        }

    }

}

통지 synchronized(this){....}초기 예제의 일부가 아닌 것을. 동기화를하더라도 1, 0그 결과를 볼 수 있습니다. 이것은 단지도 함께 것을 증명하는 것입니다 synchronized(즉, 내부적으로 제공 System.out.println), 당신은 여전히 얻을 수 있습니다 1보다 0.

1 akuzminykh Nov 24 2020 at 17:04

의 값 var이 읽혀 지면 1다시 변경되지 않습니다. 이 출력은 가시성 또는 재정렬로 인해 발생할 수 없습니다. 일어날 수있는 일은 0 0, 0 11 1입니다.

여기서 이해해야 할 핵심 사항 println은 동기화 와 관련된 것입니다. 그 방법 내부를 살펴보면 synchronized거기 가 보일 것입니다. 이 블록은 인쇄가 해당 순서로 발생하는 효과가 있습니다. 쓰기는 언제든 발생할 수 있지만 첫 번째 인쇄물은 새 값을 볼 수 var없지만 두 번째 인쇄물은 이전 값을 볼 수 있습니다. 따라서 쓰기는 두 인쇄 전, 인쇄 중간 또는 후에 만 ​​발생할 수 있습니다.

그 외에도로 var표시 volatile되지 않았거나 어떤 식 으로든 쓰기가 동기화 되지 않기 때문에 쓰기 가 전혀 표시되지 않을 것이라는 보장 은 없습니다 .

AlexRevetchi Nov 26 2020 at 15:32

여기서 누락 된 것은 해당 스레드가 실제 물리적 코어에서 실행되고 가능한 변형이 거의 없다는 사실입니다.

  1. 모든 스레드가 동일한 코어에서 실행되면 문제는 해당 3 개 명령어의 실행 순서로 축소됩니다.이 경우 1,0은 불가능하다고 생각합니다. println 실행은 동기화에 의해 생성 된 메모리 장벽으로 인해 정렬됩니다. 1,0 제외

  2. A와 B는 2 개의 다른 코어에서 실행되며, 스레드 A를 실행하는 코어가 1을 읽 자마자 위의 printlns가 주문 된 것과 같이 0을 읽을 방법이 없습니다.

  3. 스레드 A는이 두 println 사이에서 다시 예약되므로 두 번째 println은 다른 코어에서 실행됩니다. B와 동일하거나 다른 세 번째 코어에서 실행됩니다. 따라서 2 개의 println이 다른 코어에서 실행될 때 2 개의 코어가 보는 값에 따라 달라집니다. var가 동기화되지 않은 경우 (var가이 멤버의 구성원 인 경우 명확하지 않음),이 2 개의 코어는 다른 var 값을 볼 수 있습니다. 1,0에 대한 가능성이 있습니다.

그래서 이것은 캐시 일관성 문제입니다.

추신 : 저는 jvm 전문가가 아니므로 여기에 다른 일이있을 수 있습니다.

FrancescoMenzani Nov 25 2020 at 01:35

다른 답변에 추가 :

longdouble제 32 비트가 마지막 32 비트 또는 viceversa에 이전에 표시 될 수 있으므로, 원자 기입하지 않을 수있다. 따라서 완전히 다른 값이 출력 될 수 있습니다.