Twój idiom jest bezpieczny wtedy i tylko wtedy, gdy odniesienie do HashMap
jest bezpiecznie opublikowane . Zamiast czegoś odnoszącego wnętrzności HashMap
sobie, 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ą HashMap
a 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 HashMap
publikacji 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.MAP
do 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.MAP
a 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ę MAP
na:
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, MAP
a 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ć MAP
dynamicznie w czasie wykonywania. Jeśli nie musisz tego robić, po prostu zadeklaruj MAP
jako 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ć
final
pola (w tym static final
dla 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 final
pola pozwala JVM założyć, że wartość pozostaje niezmieniona przez cały okres istnienia programu i znacznie ją zoptymalizować. Użycie final
pola 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 volatile
ma 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 HashMap
plikó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ć synchronized
metody, aby uzyskać / ustawić, AtomicReference
lub 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 final
odczytem - ale są one obecnie bardzo rzadkie.