Mój nauczyciel z wyższego poziomu Java na temat wątków powiedział coś, czego nie byłem pewien.
Stwierdził, że poniższy kod niekoniecznie zaktualizuje ready
zmienną. Według niego, dwa wątki niekoniecznie współdzielą zmienną statyczną, szczególnie w przypadku, gdy każdy wątek (wątek główny versus ReaderThread
) działa na swoim własnym procesorze i dlatego nie ma tych samych rejestrów / pamięci podręcznej / itp. I jednego procesora nie zaktualizuje drugiego.
Zasadniczo powiedział, że jest możliwe, że ready
jest aktualizowany w głównym wątku, ale NIE w ReaderThread
, więc toReaderThread
będzie się zapętlać w nieskończoność.
Twierdził również, że program mógł wydrukować 0
lub 42
. Rozumiem, jak 42
można wydrukować, ale nie 0
. Wspomniał, że tak będzie w przypadkunumber
zmiennej na wartość domyślną.
Pomyślałem, że być może nie ma gwarancji, że zmienna statyczna jest aktualizowana między wątkami, ale wydaje mi się to bardzo dziwne dla Javy. Czy tworzenie ready
ulotności rozwiązuje ten problem?
Pokazał ten kod:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready) Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
źródło
Odpowiedzi:
Nie ma nic specjalnego w zmiennych statycznych, jeśli chodzi o widoczność. Jeśli są dostępne, każdy wątek może się do nich dostać, więc bardziej prawdopodobne jest, że wystąpią problemy ze współbieżnością, ponieważ są one bardziej narażone.
Istnieje problem z widocznością narzucony przez model pamięci maszyny JVM. Oto artykuł omawiający model pamięci i sposób, w jaki zapisy stają się widoczne dla wątków . Nie możesz liczyć na zmiany, które jeden wątek sprawi, że staną się widoczne dla innych wątków w odpowiednim czasie (w rzeczywistości JVM nie ma obowiązku, aby te zmiany były widoczne w ogóle dla Ciebie, w jakimkolwiek czasie), chyba że ustalisz relację zdarzenie przed .
Oto cytat z tego linku (podany w komentarzu Jed Wesley-Smith):
źródło
Mówił o widoczności i nie należy go brać zbyt dosłownie.
Zmienne statyczne są rzeczywiście współdzielone między wątkami, ale zmiany wprowadzone w jednym wątku mogą nie być natychmiast widoczne dla innego wątku, przez co wydaje się, że istnieją dwie kopie zmiennej.
Ten artykuł przedstawia pogląd, który jest zgodny z tym, jak przedstawił informacje:
Ale znowu, jest to po prostu model myślowy do myślenia o wątkach i niestabilności, a nie dosłownie jak działa JVM.
źródło
Zasadniczo to prawda, ale w rzeczywistości problem jest bardziej złożony. Na widoczność udostępnianych danych mogą wpływać nie tylko pamięci podręczne procesora, ale także wykonywanie instrukcji poza kolejnością.
Dlatego Java definiuje model pamięci , który określa, w jakich okolicznościach wątki mogą widzieć spójny stan udostępnionych danych.
W Twoim przypadku dodanie
volatile
gwarantuje widoczność.źródło
Są oczywiście „współdzielone” w tym sensie, że oboje odnoszą się do tej samej zmiennej, ale niekoniecznie widzą wzajemne aktualizacje. Dotyczy to każdej zmiennej, nie tylko statycznej.
Teoretycznie zapisy wykonane przez inny wątek mogą wydawać się w innej kolejności, chyba że zadeklarowano zmienne
volatile
lub zapisy zostały jawnie zsynchronizowane.źródło
W ramach jednego modułu ładującego klasy pola statyczne są zawsze udostępniane. Aby jawnie określić zakres danych do wątków, warto użyć narzędzia takiego jak
ThreadLocal
.źródło
Podczas inicjowania statycznej zmiennej typu pierwotnego java domyślnie przypisuje wartość zmiennym statycznym
kiedy definiujesz zmienną w ten sposób, domyślną wartością jest i = 0; dlatego istnieje możliwość uzyskania wartości 0. wtedy główny wątek aktualizuje wartość boolean ready na true. ponieważ ready jest zmienną statyczną, główny wątek i drugi wątek odwołują się do tego samego adresu pamięci, więc zmienna ready zmienia się. więc dodatkowy wątek wychodzi z pętli while i wypisuje wartość. podczas drukowania wartość zainicjowana wartością number jest 0. jeśli proces wątku przeszedł pętlę while przed zmienną numer aktualizacji głównego wątku. wtedy istnieje możliwość wydrukowania 0
źródło
@dontocsata możesz wrócić do swojego nauczyciela i trochę go wyszkolić :)
kilka notatek z prawdziwego świata i niezależnie od tego, co widzisz lub co Ci powiedzą UWAGA, poniższe słowa odnoszą się do tego konkretnego przypadku w podanej kolejności.
Następujące dwie zmienne będą znajdować się w tej samej linii pamięci podręcznej w praktycznie każdej znanej architekturze.
Thread.exit
(wątek główny) gwarantuje wyjście iexit
powoduje ograniczenie pamięci ze względu na usunięcie wątku grupy wątków (i wiele innych problemów). (jest to zsynchronizowane wywołanie i nie widzę jednego sposobu na zaimplementowanie bez części synchronizacyjnej, ponieważ ThreadGroup musi również zakończyć się, jeśli nie pozostały żadne wątki demonów itp.).Uruchomiony wątek
ReaderThread
będzie podtrzymywał proces, ponieważ nie jest demonem! Tak więcready
inumber
zostaną spłukane razem (lub numer przed, jeśli nastąpi zmiana kontekstu) i nie ma prawdziwego powodu do zmiany kolejności w tym przypadku, przynajmniej nie mogę nawet pomyśleć o jednym. Aby zobaczyć cokolwiek innego, będziesz potrzebować czegoś naprawdę dziwnego42
. Ponownie zakładam, że obie zmienne statyczne będą w tej samej linii pamięci podręcznej. Po prostu nie mogę sobie wyobrazić linii pamięci podręcznej o długości 4 bajtów LUB maszyny JVM, która nie przypisze ich w ciągłym obszarze (linia pamięci podręcznej).źródło