Bariery pamięci i styl kodowania na maszynie wirtualnej Java
Załóżmy, że mam statyczny obiekt złożony, który jest okresowo aktualizowany przez pulę wątków i czytany mniej więcej w sposób ciągły w długotrwałym wątku. Sam obiekt jest zawsze niezmienny i odzwierciedla najnowszy stan czegoś.
class Foo() { int a, b; }
static Foo theFoo;
void updateFoo(int newA, int newB) {
f = new Foo();
f.a = newA;
f.b = newB;
// HERE
theFoo = f;
}
void readFoo() {
Foo f = theFoo;
// use f...
}
Nie obchodzi mnie, czy mój czytelnik widzi stary czy nowy Foo, jednak muszę zobaczyć w pełni zainicjowany obiekt. IUC, Specyfikacja Javy mówi, że bez bariery pamięci tutaj, mogę zobaczyć obiekt z F. B zainicjowany, ale f. a jeszcze nie zapamiętywane. Mój program jest prawdziwym programem, który prędzej czy później przeczyta rzeczy do pamięci, więc nie muszę od razu przeczytywać nowej wartości theFoo do pamięci(choć nie zaszkodzi).
Jaki jest według ciebie najbardziej czytelny sposób implementacji bariery pamięci ? Jestem gotów zapłacić trochę ceny wydajności ze względu na czytelność, jeśli zajdzie taka potrzeba. Myślę, że mogę zsynchronizować zadanie z Foo i to by zadziałało, ale nie jestem pewien, czy to bardzo oczywiste dla kogoś czytającego kod, dlaczego to robię. Mógłbym również zsynchronizować całą inicjalizację nowego Foo, ale to wprowadziłoby więcej blokowania, które faktycznie potrzebne.
Jak byś to napisał, żeby było jak najbardziej czytelne ?
Bonus za wersję Scali:)
2 answers
Krótkie odpowiedzi na oryginalne pytanie
- Jeśli
Foo
jest niezmienne, po prostu ukończenie pól zapewni pełną inicjalizację i spójną widoczność pól dla wszystkich wątków, niezależnie od synchronizacji. - niezależnie od tego, czy
Foo
jest niezmienna, publikacja przezvolatile theFoo
lubAtomicReference<Foo> theFoo
jest wystarczająca, aby zapewnić, że zapisy do jej pól są widoczne dla dowolnego wątku czytającego przeztheFoo
reference - używając zwykłego przypisania do
theFoo
, wątki czytnika są nigdy gwarantujemy, że zobaczysz jakąkolwiek aktualizację
W 1995 roku, w wyniku połączenia z JCiP, powstała pierwsza w Polsce Wersja jcip, która została wprowadzona do użytku w 1996 roku, a w 1998 roku została wprowadzona do użytku w 1999 roku.]}
Niestety nie mam nic do zaoferowania w Scali]}
Możesz użyć volatile
Winię cię. Teraz jestem uzależniony, złamałem JCiP , a teraz zastanawiam się, czy jakikolwiek kod, który kiedykolwiek napisałem, jest poprawny. Powyższy fragment kodu jest potencjalnie niespójna. (Edit: zobacz sekcję poniżej na temat bezpiecznej publikacji za pośrednictwem volatile.) a
i b
były) dla nieograniczonego czasu.Możesz wykonać jedną z następujących czynności, aby wprowadzić krawędź happens-before:]}
- W ten sposób można uzyskać dostęp do informacji o tym, co dzieje się w danym miejscu, a co za tym idzie, o tym, co dzieje się w danym miejscu.]}
- użyj
final
pól i inicjalizacja wartości w konstruktorze przed publikacją - wprowadzenie zsynchronizowanego bloku podczas zapisu nowych wartości do
theFoo
obiektu - użyj
AtomicInteger
pól
To rozwiązuje kolejność zapisu (i rozwiązuje problemy z widocznością). Następnie musisz zająć się widocznością nowego odniesienia theFoo
. Tutaj volatile
jest właściwe -- jcip mówi w sekcji 3.1.4 "zmienne lotne", (a tutaj zmienna jest theFoo
):
Możesz użyć zmienne zmienne tylko wtedy, gdy spełnione są wszystkie następujące kryteria:
- zapis do zmiennej nie zależy od jej bieżącej wartości, lub możesz upewnić się, że tylko jeden wątek kiedykolwiek aktualizuje wartość;
- zmienna nie uczestniczy w niezmiennikach z innymi zmiennymi stanu; i
- Blokowanie nie jest wymagane z żadnego innego powodu, gdy zmienna jest dostępna
Jeśli wykonasz następujące czynności, jesteś złoty:
class Foo {
// it turns out these fields may not be final, with the volatile publish,
// the values will be seen under the new JMM
final int a, b;
Foo(final int a; final int b)
{ this.a = a; this.b=b; }
}
// without volatile here, separate threads A' calling readFoo()
// may never see the new theFoo value, written by thread A
static volatile Foo theFoo;
void updateFoo(int newA, int newB) {
f = new Foo(newA,newB);
theFoo = f;
}
void readFoo() {
final Foo f = theFoo;
// use f...
}
Proste i czytelne
Kilku ludzi w tym i innych wątkach (dzięki @John V) zauważ, że władze w tych kwestiach podkreślają znaczeniedokumentacji zachowania synchronizacji i założeń. JCiP mówi o tym szczegółowo, dostarcza zestaw adnotacji , które mogą być używane do dokumentacji i sprawdzania statycznego, a także można zajrzeć do JMM Cookbook dla wskaźników dotyczących konkretnych zachowań, które wymagają dokumentacji oraz odnośniki do odpowiednich odnośników. Doug Lea przygotował również listę problemów do rozważenia, kiedy {111]}dokumentuje zachowanie współbieżności {66]}. Dokumentacja jest odpowiednia szczególnie ze względu na obawy, sceptycyzm i zamieszanie związane z kwestiami współbieżności(on SO: {113]} " czy cynizm współbieżności Javy posunął się za daleko?"). Ponadto narzędzia takie jak FindBugs zapewniają teraz statyczne reguły sprawdzania, aby zauważać naruszenia semantyki adnotacji JCiP, takie jak "niespójna Synchronizacja: IS_FIELD-NOT_GUARDED".
Dopóki uważasz, że masz powód, aby zrobić inaczej, prawdopodobnie najlepiej jest przejść do najbardziej czytelnego rozwiązania, coś takiego (dzięki, @ Burleigh Bear), używając @Immutable
oraz @GuardedBy
Przypisy
@Immutable
class Foo {
final int a, b;
Foo(final int a; final int b) { this.a = a; this.b=b; }
}
static final Object FooSync theFooSync = new Object();
@GuardedBy("theFooSync");
static Foo theFoo;
void updateFoo(final int newA, final int newB) {
f = new Foo(newA,newB);
synchronized (theFooSync) {theFoo = f;}
}
void readFoo() {
final Foo f;
synchronized(theFooSync){f = theFoo;}
// use f...
}
Lub, być może, ponieważ jest czystsze:
static AtomicReference<Foo> theFoo;
void updateFoo(final int newA, final int newB) {
theFoo.set(new Foo(newA,newB)); }
void readFoo() { Foo f = theFoo.get(); ... }
Kiedy należy stosować volatile
Po pierwsze, zauważ, że to pytanie odnosi się do pytania tutaj, ale był adresowany wiele, wiele razy na tak:
- kiedy dokładnie używasz volatile?
- Czy kiedykolwiek używasz słowa kluczowego volatile w Javie
- do tego, co jest używane "lotne"
- użycie słowa kluczowego volatile
- Java volatile boolean vs. AtomicBoolean
W rzeczywistości, wyszukiwarka google: "site:stackoverflow.com + java + volatile +keyword" zwraca 355 różnych wyników. Użycie volatile
to w najlepszym razie niestabilna decyzja. Kiedy to właściwe? JCiP podaje pewne abstrakcyjne wytyczne (cytowane powyżej). Zbieram kilka praktycznych wskazówek tutaj:
volatile
can be used to safely publish immutable objects", który starannie hermetyzuje większość zakresu zastosowań, jakich można oczekiwać od programisty aplikacji.volatile
jest najbardziej przydatna w algorytmach bez blokady " podsumowuje kolejną klasę uses - algorytmy specjalnego przeznaczenia, bez blokady, które są wystarczająco wrażliwe na wydajność, aby zasługiwać na dokładną analizę i walidację przez eksperta.Bezpieczna publikacja przez lotne
Idąc dalej @ Jed Wesley-Smith, wydaje się, że volatile
zapewnia teraz silniejsze Gwarancje (od JSR-133), A wcześniejsze stwierdzenie "możesz użyć volatile
pod warunkiem, że opublikowany obiekt jest niezmienny" jest wystarczające, ale być może nie jest konieczne.
Patrząc na FAQ JMM, dwa wpisy jak działają Ostatnie pola pod nowym JMM? i co robi volatile?Nie są tak naprawdę rozpatrywane razem, ale myślę, że druga daje nam to, czego potrzebujemy:]}
Różnica polega na tym, że teraz nie
już tak łatwo zmienić kolejność normalnego pola
dostęp do nich. Pisanie do a
pole lotne ma taką samą pamięć
efekt jako zwolnienie monitora, oraz
odczyt z pola lotnego ma
taki sam efekt pamięci Jak monitor
/ align = "left" / W efekcie, ponieważ nowy
miejsca modelu pamięci
ograniczenia dotyczące zmiany kolejności lotnych
dostęp do pola z innym polem
dostęp, Lotny czy nie, cokolwiek
to było widoczne dla wątku a kiedy to
zapisuje do pola lotnego f
widoczne dla wątku B, gdy czyta f. Zauważ, że pomimo kilku przekierowań JCiP, odpowiedni tekst nie wyskoczył mi, dopóki Jed nie wskazał go. Jest na str. 38, sekcja 3.1.4, i mówi mniej więcej to samo, co poprzedni cytat - opublikowany obiekt musi być skutecznie niezmienny, bez Jeden komentarz: jakiś powód, dla którego Również, używając W dalszej recenzji, uważam, że komentarz od @ Burleigh Bear powyżej jest poprawny - - - (EDIT: patrz poniżej) Ale w tym przypadku problemy z synchronizacją są dość przejrzyste, a zamawianie jest najmniejszym z twoich zmartwień. Aby uzyskać przydatne wskazówki na temat volatile, zapoznaj się z tym artykułem developerWorks. Jednakże, możesz mieć problem, gdzie osobne wątki czytnika mogą zobaczyć starą wartość
final
pól wymaganych, QED. Starsze rzeczy, trzymane za odpowiedzialność
newA
i newB
nie mogą być argumentami dla konstruktora? Wtedy możesz polegać na regułach publikacji dla konstruktorów...AtomicReference
prawdopodobnie usuwa wszelką niepewność (i może kupić ci inne korzyści w zależności od tego, co musisz zrobić w pozostałej części klasy... Ponadto, ktoś mądrzejszy ode mnie może Ci powiedzieć, czy to rozwiąże, ale zawsze wydaje mi się to tajemnicze... nie musisz się martwić o kolejność out-of-sequence, ponieważ publikujesz nowy obiekt do theFoo
. Podczas gdy inny wątek może prawdopodobnie zobaczyć niespójne wartości dla newA
i newB
, jak opisano w JLS 17.11, to nie może dzieje się tak, ponieważ zostaną one zapisane w pamięci, zanim drugi wątek uzyska odniesienie do nowej instancji f = new Foo()
, którą utworzyłeś... jest to bezpieczna Jednorazowa Publikacja. Z drugiej strony, jeśli napisałeś void updateFoo(int newA, int newB) {
f = new Foo(); theFoo = f;
f.a = newA; f.b = newB;
}
theFoo
dla nieograniczonej ilości czasu. W praktyce zdarza się to rzadko. Jednak JVM może mieć możliwość buforowania wartości odniesienia theFoo
w kontekście innego wątku. Jestem pewien, że Oznaczenie theFoo
jako volatile
zajmie się tym, podobnie jak każdy rodzaj synchronizatora lub AtomicReference
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:08
Posiadanie niezmiennego Foo z końcowymi polami a i b rozwiązuje problemy z widocznością z wartościami domyślnymi, ale również sprawia, że foo jest niestabilne.
Osobiście lubię mieć niezmienne klasy wartości i tak, ponieważ znacznie trudniej je nadużywać.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-19 00:17:03