Twój idiom jest bezpieczny wtedy i tylko wtedy, gdy odniesienie do HashMapjest bezpiecznie opublikowane . Zamiast czegoś odnoszącego wnętrzności HashMapsobie, bezpieczne publikacji zajmuje się jak nić budowy sprawia, że odniesienie do mapy widocznej na innych wątkach.
Zasadniczo jedyna możliwa wyścig jest tutaj między budową HashMapa wszelkimi wątkami czytającymi, które mogą uzyskać do niego dostęp, zanim zostanie w pełni skonstruowany. Większość dyskusji dotyczy tego, co dzieje się ze stanem obiektu mapy, ale nie ma to znaczenia, ponieważ nigdy go nie modyfikujesz - więc jedyną interesującą częścią jest sposób HashMappublikacji odniesienia.
Na przykład wyobraź sobie, że publikujesz mapę w ten sposób:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... iw pewnym momencie setMap()jest wywoływana z mapą, a inne wątki używają SomeClass.MAPdo uzyskania dostępu do mapy i sprawdzają wartość null w następujący sposób:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
Nie jest to bezpieczne, chociaż prawdopodobnie wydaje się, że tak jest. Problem polega na tym, że nie ma związku między zbiorem SomeObject.MAPa kolejnym odczytem w innym wątku, więc wątek odczytu może zobaczyć częściowo skonstruowaną mapę. Może to zrobić prawie wszystko, a nawet w praktyce robi takie rzeczy, jak umieszczanie wątku odczytu w nieskończonej pętli .
Aby bezpiecznie opublikować mapę, musisz ustanowić relację zdarzają się przed zapisaniem odniesienia do HashMap(tj. Publikacji ) a późniejszymi czytelnikami tego odniesienia (tj. Konsumpcją). Dogodnie, istnieje tylko kilka łatwych do zapamiętania sposobów wykonać za które [1] :
- Wymień odniesienie poprzez odpowiednio zablokowane pole ( JLS 17.4.5 )
- Użyj statycznego inicjatora do inicjalizacji sklepów ( JLS 12.4 )
- Zamień odniesienie przez zmienne pole ( JLS 17.4.5 ) lub w konsekwencji tej reguły za pośrednictwem klas AtomicX
- Zainicjuj wartość w ostatnim polu ( JLS 17,5 ).
Te najbardziej interesujące dla twojego scenariusza to (2), (3) i (4). W szczególności (3) odnosi się bezpośrednio do kodu, który mam powyżej: jeśli przekształcisz deklarację MAPna:
public static volatile HashMap<Object, Object> MAP;
wtedy wszystko jest koszerne: czytelnicy, którzy widzą wartość różną od null, muszą koniecznie mieć relację „ zdarzenie przed” ze sklepem, MAPa tym samym zobaczyć wszystkie sklepy powiązane z inicjalizacją mapy.
Inne metody zmieniają semantykę twojej metody, ponieważ zarówno (2) (przy użyciu statycznego inicjatora), jak i (4) (przy użyciu final ) sugerują, że nie można ustawiać MAPdynamicznie w czasie wykonywania. Jeśli nie musisz tego robić, po prostu zadeklaruj MAPjako static final HashMap<>i masz gwarancję bezpiecznej publikacji.
W praktyce zasady bezpiecznego dostępu do „obiektów nigdy nie modyfikowanych” są proste:
Jeśli publikujesz obiekt, który nie jest z natury niezmienny (jak we wszystkich zadeklarowanych polach final) i:
- Możesz już stworzyć obiekt, który zostanie przypisany w momencie deklaracji a : wystarczy użyć
finalpola (w tym static finaldla statycznych składowych).
- Chcesz przypisać obiekt później, gdy referencja jest już widoczna: użyj zmiennego pola b .
Otóż to!
W praktyce jest bardzo wydajny. Na przykład użycie static finalpola pozwala JVM założyć, że wartość pozostaje niezmieniona przez cały okres istnienia programu i znacznie ją zoptymalizować. Użycie finalpola składowego umożliwia większości architektur odczytywanie pola w sposób równoważny z normalnym odczytem pola i nie hamuje dalszych optymalizacji . C.
Wreszcie, użycie programu volatilema pewien wpływ: na wielu architekturach (takich jak x86, szczególnie te, które nie pozwalają odczytom na przekazywanie odczytów) nie jest potrzebna bariera sprzętowa, ale pewna optymalizacja i zmiana kolejności mogą nie wystąpić w czasie kompilacji - ale to efekt jest na ogół niewielki. W zamian otrzymujesz więcej niż to, o co prosiłeś - nie tylko możesz bezpiecznie opublikować jedną HashMap, ale możesz przechowywać dowolną liczbę niezmodyfikowanych HashMapplików w tym samym numerze referencyjnym i mieć pewność, że wszyscy czytelnicy zobaczą bezpiecznie opublikowaną mapę .
Więcej krwawych informacji można znaleźć w Shipilev lub w tym FAQ autorstwa Mansona i Goetza .
[1] Cytat bezpośrednio z Shipilev .
a To brzmi skomplikowanie, ale mam na myśli to, że można przypisać odwołanie w czasie tworzenia - albo w punkcie deklaracji, albo w konstruktorze (pola składowe) lub inicjalizatorze statycznym (pola statyczne).
b Opcjonalnie możesz użyć synchronizedmetody, aby uzyskać / ustawić, AtomicReferencelub coś w tym stylu, ale mówimy o minimalnej pracy, którą możesz wykonać.
c Niektóre architektury z bardzo słabymi modelami pamięci (patrzę na ciebie , Alpha) mogą wymagać pewnego rodzaju bariery odczytu przed finalodczytem - ale są one obecnie bardzo rzadkie.