2015-07-04 13 views
12

Obecnie czytam JSR-133 (model pamięci Java) i nie mogę zrozumieć, dlaczego f.y może być niezainicjowany (może zobaczyć 0). Czy ktoś może mi to wytłumaczyć?Inicjowanie nieostatniego pola

class FinalFieldExample { 
    final int x; 
    int y; 
    static FinalFieldExample f; 

    public FinalFieldExample() { 
     x = 3; 
     y = 4; 
    } 

    static void writer() { 
     f = new FinalFieldExample(); 
    } 

    static void reader() { 
     if (f != null) { 
      int i = f.x; // guaranteed to see 3 
      int j = f.y; // could see 0 
     } 
    } 
} 
+0

Wiem, że to dotyczy maszyny JVM, która może zmieniać kolejność zapisów, ale nie znam szczegółów. : - | –

+0

Ponieważ myślę, że nie przeczytałeś poprawnie jsr133, jest to wyraźnie wspomniane. "Nie pisz referencji do konstruowanego obiektu w miejscu, gdzie inny wątek może go zobaczyć przed zakończeniem konstruktora obiektu. Jeśli tak, to gdy inny obiekt zobaczy obiekt , ten wątek zawsze wyświetli poprawnie skonstruowaną wersję końcowych pól tego obiektu, ale w przypadku nieostatniego pola wątek może zobaczyć wartość domyślną. – Prashant

+0

tak, to jest szalone. mam nadzieję, że naprawią to w java9. – ZhongYu

Odpowiedz

-1

model pamięci Java pozwala wątku, aby utworzyć FinalFieldExample zainicjować końcowego x oraz zapisać dane na przykład FinalFieldExample do f pole przed inicjalizacji Nieprawomocnym y.

+0

Co jeśli pole 'x' zostało zainicjowane po' y' w konstruktorze? –

+0

Przekazywanie instrukcji jest dozwolone, dlatego też Java może publikować niezainicjowane instancje. –

+0

Ta odpowiedź nie przydaje się bez cytatów, odpowiednich cytatów i innych wyjaśnień. W odpowiedzi nie ma żadnych informacji, których nie ma w pytaniu. –

7

Nazywa się to efektem "przedwczesnej publikacji".

Mówiąc prościej, JVM może zmienić kolejność instrukcji programu (ze względu na wydajność), jeśli takie uporządkowanie nie narusza ograniczeń JMM.

Oczekujesz kod f = new FinalFieldExample(); biec jak:

1. utworzyć instancję FinalFieldExample
2. Przypisanie do 3 x
3. Nadać 4 do y
4. przywiąż utworzony obiekt do zmiennej f

Ale w kodzie przewidzianym, nic nie może zatrzymać JVM z instrukcją zmiany kolejności, więc może uruchomić kod jak:

1. utworzyć egzemplarz FinalFieldExample
2. Przypisanie do 3 x
3. przypisać surowe, nie w pełni zainicjowany obiektu zmiennej f
4. Przypisanie 4 Y

Jeżeli zmiana kolejności dzieje się w pojedynczym środowisko wątków, nawet tego nie zauważymy. To dlatego, że oczekujemy, że obiekty zostaną w pełni stworzone, zanim zaczniemy z nimi pracować, a JVM szanuje nasze oczekiwania. Co może się stać, jeśli kilka wątków uruchomi ten kod jednocześnie? W kolejnym przykładzie thread1 jest sposób writer() i thread2 wykonania - metoda reader():

wątku 1: tworzenie wystąpienie FinalFieldExample
gwintu 1: Rola 3-X
gwintu 1: przypisanie surowe, nie w pełni zainicjowany obiektu zmiennej f
gwintu 2: odczytywanie f, nie jest zerowy
gwintu 2: odczytywanie Fx jest 3
gwintu 2: odczytywanie fy jest nadal 0
gwintu 1: przypisanie 4 Y

Zdecydowanie nie jest dobra. Aby nie dopuścić do tego JVM, musimy podać dodatkowe informacje o programie. W tym konkretnym przykładzie, istnieje kilka sposobów, aby naprawić spójności pamięci:

  • declare y jako final zmienna. Spowoduje to efekt "freeze". W skrócie, ostateczne zmienne będą zawsze inicjowane w momencie, gdy uzyskasz do nich dostęp, , jeśli odwołanie do obiektu nie wyciekło podczas budowy.
  • zadeklaruj f jako zmienną volatile.Spowoduje to utworzenie "synchronization order" i naprawienie problemu. W skrócie, instrukcje nie mogą zostać zmienione poniżej volatile write i powyżej volatile read. Przypisanie do zmiennej f jest zmiennym, co oznacza, że ​​instrukcje new FinalFieldExample() nie mogą zostać zmienione i wykonane po przypisaniu. Odczyt ze zmiennej f jest zmiennym odczytem, ​​więc odczyt f.x nie może być wykonany przed nim. Kombinacja v-write i v-read nazywa się kolejnością synchronizacji i zapewnia pożądaną spójność pamięci.

Here to dobry blog, który może odpowiedzieć na wszystkie pytania dotyczące JMM.

+0

Jeśli wywołanie konstruktora może się zdarzyć po opublikowaniu odwołania do obiektu do 'f', w jaki sposób JVM spełnia _guaranteed, aby zobaczyć 3_? –

+0

@SotiriosDelimanoljest to tak zwana "akcja zamrożenia". W skrócie, jeśli w konstruktorze jest przypisane ostatnie pole, JMM zostanie zmuszone do wykonania go przed opublikowaniem odwołania do nowego obiektu. – AdamSkywalker

+0

Gdybyśmy mieli "y = 4" przed przypisaniem do "ostatecznego" pola 'x', czy to zatrzymanie akcji zagwarantowałoby zainicjowanie' y'? Czy JVM może zmienić kolejność po przypisaniu 'x' i opublikować obiekt, pozostawiając" y "potencjalnie niezainicjowany? –

1

JVM może zmienić kolejność odczytów i zapisów w pamięci, więc odniesienie do f może zostać zapisane w pamięci głównej przed wartością f.y. Jeśli inny wątek czyta f.y między tymi dwoma zapisami, będzie czytać 0. Jeśli jednak utworzysz barierę pamięci, pisząc pole final lub volatile, odczytuje i zapisuje po tym, jak bariera nie może zostać zmieniona w odniesieniu do odczytu i zapisu przed barierą. Gwarantuje to, że zarówno f, jak i f.y zostaną zapisane przed kolejnym wątkiem: f.

Poprosiłem o podobne pytanie here. Odpowiedzi są dużo bardziej szczegółowe.