Czy pobieranie wartości z java.util.HashMap z wielu wątków jest bezpieczne (bez modyfikacji)?

138

Istnieje przypadek, w którym mapa zostanie skonstruowana i po zainicjowaniu nigdy nie zostanie ponownie zmodyfikowana. Dostęp do niego będzie jednak możliwy (tylko przez get (klucz)) z wielu wątków. Czy używanie java.util.HashMapw ten sposób jest bezpieczne ?

(Obecnie szczęśliwie używam a java.util.concurrent.ConcurrentHashMapi nie mam wymiernej potrzeby poprawy wydajności, ale jestem po prostu ciekawy, czy HashMapwystarczyłoby proste . Dlatego pytanie to nie brzmi „Którego powinienem użyć?” Ani nie jest to kwestia wydajności. Raczej pytanie brzmi: „Czy byłoby to bezpieczne?”)

Dave L.
źródło
4
Wiele odpowiedzi jest poprawnych w odniesieniu do wzajemnego wykluczania z uruchomionych wątków, ale niepoprawnych w zakresie aktualizacji pamięci. Odpowiednio zagłosowałem w górę / w dół, ale wciąż jest wiele błędnych odpowiedzi z pozytywnymi głosami.
Heath Borders
@Heath Borders, jeśli instancja a została zainicjowana statycznie niemodyfikowalna HashMap, powinna być bezpieczna dla jednoczesnego odczytu (ponieważ inne wątki nie mogły przegapić aktualizacji, ponieważ nie było aktualizacji), prawda?
kaqqao
Jeśli jest zainicjowany statycznie i nigdy nie jest modyfikowany poza blokiem statycznym, może być w porządku, ponieważ cała inicjalizacja statyczna jest synchronizowana przez ClassLoader. Samo to jest warte osobnego pytania. Nadal jawnie zsynchronizowałbym go i profil, aby sprawdzić, czy powoduje rzeczywiste problemy z wydajnością.
Heath Borders
@HeathBorders - co rozumiesz przez „aktualizacje pamięci”? JVM to model formalny, który definiuje takie rzeczy, jak widoczność, niepodzielność, relacje wydarzy się przed , ale nie używa terminów takich jak „aktualizacje pamięci”. Należy wyjaśnić, najlepiej używając terminologii z JLS.
BeeOnRope
2
@Dave - zakładam, że po 8 latach nadal nie szukasz odpowiedzi, ale dla przypomnienia, kluczowym pomyłką w prawie wszystkich odpowiedziach jest to, że koncentrują się one na działaniach podejmowanych na obiekcie mapy . Wyjaśniłeś już, że nigdy nie modyfikujesz obiektu, więc to wszystko nie ma znaczenia. Jedynym potencjalnym rozwiązaniem jest sposób, w jaki publikujesz odniesienie do pliku Map, którego nie wyjaśniłeś. Jeśli nie zrobisz tego bezpiecznie, nie jest to bezpieczne. Jeśli robisz to bezpiecznie, to tak . Szczegóły w mojej odpowiedzi.
BeeOnRope

Odpowiedzi:

55

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] :

  1. Wymień odniesienie poprzez odpowiednio zablokowane pole ( JLS 17.4.5 )
  2. Użyj statycznego inicjatora do inicjalizacji sklepów ( JLS 12.4 )
  3. Zamień odniesienie przez zmienne pole ( JLS 17.4.5 ) lub w konsekwencji tej reguły za pośrednictwem klas AtomicX
  4. 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.

BeeOnRope
źródło
never modify HashMapnie oznacza, że state of the map objectjest bezpieczny dla wątku. Bóg zna wdrożenie biblioteki, jeśli oficjalny dokument nie mówi, że jest bezpieczna dla wątków.
Jiang YD
@JiangYD - masz rację, w niektórych przypadkach jest tam szara strefa: kiedy mówimy „modyfikuj”, tak naprawdę mamy na myśli każdą akcję, która wewnętrznie wykonuje pewne zapisy, które mogą ścigać się z odczytami lub zapisami w innych wątkach. Te zapisy mogą być wewnętrznymi szczegółami implementacji, więc nawet operacja, która wydaje się być „tylko do odczytu”, get()może w rzeczywistości wykonać pewne zapisy, na przykład aktualizację niektórych statystyk (lub w przypadku LinkedHashMapaktualizacji kolejności dostępu z poleceniem dostępu). Tak więc dobrze napisane zajęcia powinny zawierać dokumentację, która wyjaśnia, czy ...
BeeOnRope
... najwyraźniej operacje „tylko do odczytu” są wewnętrznie tylko do odczytu w sensie bezpieczeństwa wątków. Na przykład w bibliotece standardowej C ++ istnieje ogólna zasada, że ​​zaznaczone funkcje składowe constsą w tym sensie naprawdę tylko do odczytu (wewnętrznie mogą nadal wykonywać zapisy, ale będą musiały być zabezpieczone wątkowo). W constJavie nie ma słowa kluczowego i nie znam żadnej udokumentowanej ogólnej gwarancji, ale generalnie standardowe klasy bibliotek zachowują się zgodnie z oczekiwaniami, a wyjątki są udokumentowane (zobacz LinkedHashMapprzykład, w którym operacje RO getsą wyraźnie wymienione jako niebezpieczne).
BeeOnRope
@JiangYD - w końcu wracając do pierwotnego pytania, ponieważ HashMaptak naprawdę mamy w dokumentacji zachowanie bezpieczeństwa wątków dla tej klasy: Jeśli wiele wątków jednocześnie uzyskuje dostęp do mapy mieszania, a co najmniej jeden z wątków modyfikuje strukturę mapy, musi być zsynchronizowany zewnętrznie. (Modyfikacja strukturalna to dowolna operacja, która dodaje lub usuwa jedno lub więcej mapowań; sama zmiana wartości powiązanej z kluczem, który już zawiera instancja, nie jest modyfikacją strukturalną.)
BeeOnRope
Dlatego HashMapmetody, które powinny być tylko do odczytu, są tylko do odczytu, ponieważ nie modyfikują strukturalnie pliku HashMap. Oczywiście ta gwarancja może nie obowiązywać w przypadku dowolnych innych Mapimplementacji, ale pytanie dotyczy HashMapkonkretnie.
BeeOnRope
70

Jeremy Manson, bóg, jeśli chodzi o model pamięci w Javie, prowadzi na ten temat trzyczęściowy blog - ponieważ w istocie zadajesz pytanie „Czy dostęp do niezmiennej mapy HashMap jest bezpieczny” - odpowiedź na to pytanie brzmi: tak. Ale musisz odpowiedzieć na predykat na to pytanie, które brzmi - „Czy moja mapa HashMap jest niezmienna”. Odpowiedź może Cię zaskoczyć - Java ma stosunkowo skomplikowany zestaw reguł określania niezmienności.

Aby uzyskać więcej informacji na ten temat, przeczytaj posty na blogu Jeremy'ego:

Część 1 dotycząca niezmienności w Javie: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Część 2 dotycząca niezmienności w Javie: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Część 3 dotycząca niezmienności w Javie: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html

Taylor Gautier
źródło
3
To dobra uwaga, ale opieram się na inicjalizacji statycznej, podczas której żadne odwołania nie uciekają, więc powinno być bezpieczne.
Dave L.,
5
Nie widzę, jak to jest wysoko oceniana odpowiedź (a nawet odpowiedź). Po pierwsze, nie odpowiada nawet na pytanie i nie wspomina o jednej kluczowej zasadzie, która zadecyduje o tym, czy jest to bezpieczne: bezpieczna publikacja . „Odpowiedź” sprowadza się do „to trudne” i oto trzy (złożone) linki, które możesz przeczytać.
BeeOnRope
Odpowiada na pytanie na samym końcu pierwszego zdania. Jeśli chodzi o odpowiedź, zwraca uwagę, że niezmienność (o której mowa w pierwszym akapicie pytania) nie jest prosta, wraz z cennymi zasobami, które wyjaśniają ten temat dalej. Punkty nie mierzą tego, czy jest to odpowiedź, mierzą, czy odpowiedź była „przydatna” dla innych. Zaakceptowana odpowiedź oznacza, że ​​była to odpowiedź, której szukał OP, a która otrzymała Twoją odpowiedź.
Jesse
@Jesse nie odpowiada na pytanie na końcu pierwszego zdania, odpowiada na pytanie „czy dostęp do niezmiennego obiektu jest bezpieczny”, co może, ale nie musi, dotyczyć pytania PO, jak wskazuje w następnym zdaniu. Zasadniczo jest to odpowiedź typu „idź samemu wymyślić”, prawie tylko łącze, co nie jest dobrą odpowiedzią na SO. Jeśli chodzi o głosy za, myślę, że jest to bardziej funkcja bycia 10,5 roku i często wyszukiwanym tematem. W ciągu ostatnich kilku lat otrzymał on zaledwie kilka głosów za pozytywnymi opiniami, więc może ludzie będą tu przychodzić :).
BeeOnRope
35

Odczyty są bezpieczne z punktu widzenia synchronizacji, ale nie z punktu widzenia pamięci. Jest to coś, co jest powszechnie źle rozumiane przez programistów Java, w tym tutaj w Stackoverflow. (Obserwuj ocenę tej odpowiedzi, aby uzyskać dowód).

Jeśli masz uruchomione inne wątki, mogą nie widzieć zaktualizowanej kopii mapy HashMap, jeśli nie ma żadnego zapisu do pamięci z bieżącego wątku. Zapisy do pamięci są wykonywane przy użyciu zsynchronizowanych lub nietrwałych słów kluczowych lub przy użyciu niektórych konstrukcji współbieżności języka Java.

Więcej informacji można znaleźć w artykule Briana Goetza na temat nowego modelu pamięci Java .

Heath Borders
źródło
Przepraszam za podwójne złożenie Heatha, zauważyłem twoje dopiero po przesłaniu mojego. :)
Alexander
2
Cieszę się, że są tu inni ludzie, którzy naprawdę rozumieją skutki pamięci.
Heath Borders
1
Rzeczywiście, chociaż żaden wątek nie zobaczy obiektu przed jego prawidłową inicjalizacją, więc nie sądzę, aby było to problemem w tym przypadku.
Dave L.,
1
Zależy to całkowicie od sposobu inicjalizacji obiektu.
Bill Michell,
1
Pytanie mówi, że po zainicjowaniu HashMap nie zamierza już go aktualizować. Odtąd chce tylko używać go jako struktury danych tylko do odczytu. Myślę, że byłoby to bezpieczne, pod warunkiem, że dane przechowywane w jego Mapie są niezmienne.
Binita Bharati
9

Po dokładniejszym przyjrzeniu się, znalazłem to w dokumentacji java (moje podkreślenie):

Zauważ, że ta implementacja nie jest zsynchronizowana. Jeśli wiele wątków jednocześnie uzyskuje dostęp do mapy skrótów, a co najmniej jeden z wątków modyfikuje mapę strukturalnie, musi być zsynchronizowany zewnętrznie. (Modyfikacja strukturalna to dowolna operacja, która dodaje lub usuwa jedno lub więcej mapowań; sama zmiana wartości skojarzonej z kluczem, który już zawiera instancja, nie jest modyfikacją strukturalną).

Wydaje się to sugerować, że będzie to bezpieczne, zakładając, że stwierdzenie jest odwrotne do prawdy.

Dave L.
źródło
1
Chociaż jest to doskonała rada, jak podają inne odpowiedzi, istnieje bardziej zniuansowana odpowiedź w przypadku niezmiennej, bezpiecznie opublikowanej instancji mapy. Ale powinieneś to robić tylko wtedy, gdy wiesz, co robisz.
Alex Miller,
1
Mam nadzieję, że dzięki takim pytaniom więcej z nas dowie się, co robimy.
Dave L.,
To nie jest poprawne. Jako stan innych odpowiedzi, musi istnieć dzieje, zanim pomiędzy ostatniej modyfikacji i całej późniejszej „bezpieczny wątku” czyta. Zwykle oznacza to, że musisz bezpiecznie opublikować obiekt po jego utworzeniu i dokonaniu jego modyfikacji. Zobacz pierwszą, zaznaczoną poprawną odpowiedź.
markspace
9

Należy zauważyć, że w pewnych okolicznościach metoda get () z niezsynchronizowanej HashMap może powodować nieskończoną pętlę. Może się tak zdarzyć, jeśli współbieżna metoda put () powoduje ponowne zhaszowanie mapy.

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html

Alex Miller
źródło
1
Właściwie widziałem, jak to zawiesiło JVM bez zużywania procesora (co jest być może gorsze)
Peter Lawrey
2
Myślę, że ten kod został przepisany w taki sposób, że nie można już uzyskać nieskończonej pętli. Ale nadal nie powinieneś uderzać w pobieranie i wstawianie z niezsynchronizowanej HashMap z innych powodów.
Alex Miller
@AlexMiller, nawet poza innymi powodami (zakładam, że odnosisz się do bezpiecznego publikowania), nie sądzę, aby zmiana implementacji była powodem do poluzowania ograniczeń dostępu, chyba że jest to wyraźnie dozwolone w dokumentacji. Tak się składa, że plik Javadoc HashMap dla Java 8 nadal zawiera następujące ostrzeżenie:Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
shmosel
8

Jest jednak ważny zwrot akcji. Dostęp do mapy jest bezpieczny, ale generalnie nie ma gwarancji, że wszystkie wątki zobaczą dokładnie ten sam stan (a tym samym wartości) HashMap. Może się to zdarzyć w systemach wieloprocesorowych, w których modyfikacje HashMap wykonane przez jeden wątek (np. Ten, który ją zapełnił) mogą znajdować się w pamięci podręcznej tego procesora i nie będą widoczne dla wątków działających na innych procesorach, dopóki operacja ogrodzenia pamięci nie zostanie wykonywane z zapewnieniem spójności pamięci podręcznej. Specyfikacja języka Java jest jasna w tym przypadku: rozwiązaniem jest uzyskanie blokady (zsynchronizowanej (...)), która emituje operację ogrodzenia pamięci. Tak więc, jeśli jesteś pewien, że po wypełnieniu HashMap każdy z wątków uzyskuje DOWOLNĄ blokadę, od tego momentu można uzyskać dostęp do HashMap z dowolnego wątku, aż do ponownej modyfikacji HashMap.

Alexander
źródło
Nie jestem pewien, czy wątek uzyskujący dostęp do niego uzyska jakąkolwiek blokadę, ale jestem pewien, że nie otrzymają odwołania do obiektu, dopóki nie zostanie zainicjowany, więc nie sądzę, aby mogli mieć nieaktualną kopię.
Dave L.,
@Alex: odwołanie do HashMap może być ulotne, aby stworzyć te same gwarancje widoczności pamięci. @Dave: Możliwe jest zobaczenie odniesień do nowych obiektów, zanim praca jego autora stanie się widoczna dla twojego wątku.
Chris Vest,
@Christian W ogólnym przypadku z pewnością. Mówiłem, że w tym kodzie tak nie jest.
Dave L.
Uzyskanie blokady RANDOM nie gwarantuje wyczyszczenia całej pamięci podręcznej procesora wątku. Zależy to od implementacji maszyny JVM i najprawdopodobniej nie jest to zrobione w ten sposób.
Pierre
Zgadzam się z Pierrem, nie sądzę, że zakup jakiegokolwiek zamka wystarczy. Musisz zsynchronizować się na tym samym zamku, aby zmiany stały się widoczne.
damluar
5

Zgodnie z http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Bezpieczeństwo inicjalizacji możesz uczynić swój HashMap ostatnim polem i po zakończeniu konstruktora zostanie bezpiecznie opublikowany.

... W nowym modelu pamięci istnieje coś podobnego do relacji zdarzenie przed zapisem między zapisem końcowego pola w konstruktorze a początkowym ładowaniem współdzielonego odniesienia do tego obiektu w innym wątku. ...

bodrin
źródło
Ta odpowiedź jest niskiej jakości, jest taka sama jak odpowiedź od @taylor gauthier, ale z mniejszą liczbą szczegółów.
Snicolas
1
Ummmm ... nie być dupkiem, ale masz to na odwrót. Taylor powiedział "nie, spójrz na ten wpis na blogu, odpowiedź może cię zaskoczyć", podczas gdy ta odpowiedź faktycznie dodaje coś nowego, czego nie wiedziałem ... O relacji zdarza się przed zapisem końcowego pola w konstruktor. Ta odpowiedź jest doskonała i cieszę się, że ją przeczytałem.
Ajax
Co? To jedyna poprawna odpowiedź, jaką znalazłem po przewinięciu wyżej ocenionych odpowiedzi. Klucz jest bezpiecznie opublikowany i jest to jedyna odpowiedź, która nawet o nim wspomina.
BeeOnRope
3

Tak więc scenariusz, który opisałeś, polega na tym, że musisz umieścić zbiór danych w mapie, a kiedy skończysz ją wypełniać, traktujesz ją jako niezmienną. Jednym z podejść, które jest „bezpieczne” (co oznacza, że ​​wymuszasz, aby było traktowane jako niezmienne) jest zastąpienie odniesienia Collections.unmodifiableMap(originalMap)momentem gotowości do uczynienia go niezmiennym.

Aby zapoznać się z przykładem tego, jak źle mapy mogą zawieść, jeśli są używane jednocześnie, oraz sugerowane obejście, o którym wspomniałem, sprawdź ten wpis dotyczący parady błędów: bug_id = 6423457

Będzie
źródło
2
Jest to „bezpieczne”, ponieważ wymusza niezmienność, ale nie rozwiązuje problemu bezpieczeństwa wątków. Jeśli dostęp do mapy jest bezpieczny przy użyciu opakowania UnmodifiableMap, to jest bezpieczny bez niego i na odwrót.
Dave L.
2

To pytanie jest omówione w książce Briana Goetza „Java Concurrency in Practice” (Listing 16.8, s. 350):

@ThreadSafe
public class SafeStates {
    private final Map<String, String> states;

    public SafeStates() {
        states = new HashMap<String, String>();
        states.put("alaska", "AK");
        states.put("alabama", "AL");
        ...
        states.put("wyoming", "WY");
    }

    public String getAbbreviation(String s) {
        return states.get(s);
    }
}

Ponieważ statesjest zadeklarowany jako, finala jego inicjalizacja jest wykonywana w konstruktorze klasy właściciela, każdy wątek, który później odczytuje tę mapę, ma gwarancję, że zobaczy ją w momencie zakończenia konstruktora, pod warunkiem, że żaden inny wątek nie będzie próbował zmodyfikować zawartości mapy.

escudero380
źródło
1

Ostrzegamy, że nawet w kodzie jednowątkowym zastąpienie ConcurrentHashMap przez HashMap może nie być bezpieczne. ConcurrentHashMap zabrania null jako klucza lub wartości. HashMap ich nie zabrania (nie pytaj).

Tak więc w mało prawdopodobnej sytuacji, gdy istniejący kod może dodać wartość null do kolekcji podczas instalacji (prawdopodobnie w przypadku awarii), zastąpienie kolekcji zgodnie z opisem zmieni zachowanie funkcjonalne.

To powiedziawszy, pod warunkiem, że nie robisz nic innego, równoczesne odczyty z HashMap są bezpieczne.

[Edycja: przez „współbieżne odczyty” rozumiem, że nie ma też jednoczesnych modyfikacji.

Inne odpowiedzi wyjaśniają, jak to zapewnić. Jednym ze sposobów jest uczynienie mapy niezmienną, ale nie jest to konieczne. Na przykład model pamięci JSR133 jawnie definiuje rozpoczęcie wątku jako akcję synchroniczną, co oznacza, że ​​zmiany wprowadzone w wątku A przed rozpoczęciem wątku B są widoczne w wątku B.

Nie zamierzam zaprzeczać bardziej szczegółowym odpowiedziom na temat modelu pamięci Java. Ta odpowiedź ma na celu zwrócenie uwagi, że nawet poza kwestiami współbieżności istnieje co najmniej jedna różnica API między ConcurrentHashMap i HashMap, która może zlikwidować nawet program jednowątkowy, który zastąpił jeden z nich.]

Steve Jessop
źródło
Dziękuję za ostrzeżenie, ale nie ma żadnych prób użycia pustych kluczy ani wartości.
Dave L.,
Myślałem, że nie będzie. wartości null w kolekcjach to szalony zakątek Javy.
Steve Jessop,
Nie zgadzam się z tą odpowiedzią. „Jednoczesne odczyty z HashMap są bezpieczne” samo w sobie jest niepoprawne. Nie określa, czy odczyty występują na mapie, która jest zmienna czy niezmienna. Aby było poprawne, powinno brzmieć: „Równoczesne odczyty z niezmiennej mapy HashMap są bezpieczne”
Taylor Gautier
2
Nie zgodnie z artykułami, do których sam się odnosiłeś: wymóg jest taki, że mapa nie może być zmieniana (a poprzednie zmiany muszą być widoczne dla wszystkich wątków czytelnika), a nie, że jest niezmienna (co jest terminem technicznym w Javie i jest warunek wystarczający, ale niekonieczny dla bezpieczeństwa).
Steve Jessop
Również uwaga ... inicjalizacja klasy niejawnie synchronizuje się z tą samą blokadą (tak, można zakleszczać się w statycznych inicjalizatorach pól), więc jeśli inicjalizacja odbywa się statycznie, nikt inny nie mógłby jej zobaczyć przed zakończeniem inicjalizacji, ponieważ musiałyby być zablokowane w metodzie ClassLoader.loadClass na tej samej uzyskanej blokadzie ... A jeśli zastanawiasz się, czy różne programy ładujące klasy mają różne kopie tego samego pola, miałbyś rację ... ale byłoby to ortogonalne do pojęcie warunków wyścigu; pola statyczne modułu ładującego klasy współużytkują ogrodzenie pamięci.
Ajax
0

http://www.docjar.com/html/api/java/util/HashMap.java.html

tutaj jest źródło HashMap. Jak widać, nie ma tam absolutnie żadnego kodu blokującego / mutex.

Oznacza to, że chociaż można czytać z HashMap w sytuacji wielowątkowej, zdecydowanie użyłbym ConcurrentHashMap, gdyby było wiele zapisów.

Co ciekawe, zarówno .NET HashTable, jak i Dictionary <K, V> mają wbudowany kod synchronizacji.

FlySwat
źródło
2
Myślę, że są klasy, w których zwykłe jednoczesne czytanie może wpędzić cię w kłopoty, na przykład z powodu wewnętrznego użycia tymczasowych zmiennych instancji. Więc prawdopodobnie trzeba dokładnie zbadać źródło, więcej niż szybkie skanowanie w poszukiwaniu kodu blokującego / mutex.
Dave L.,
0

Jeśli inicjalizacja i każde wprowadzenie jest zsynchronizowane, zapisujesz.

Poniższy kod jest zapisywany, ponieważ classloader zajmie się synchronizacją:

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

Poniższy kod jest zapisywany, ponieważ zapis volatile zajmie się synchronizacją.

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

Będzie to również działać, jeśli zmienna składowa jest ostateczna, ponieważ final jest również niestabilna. A jeśli metoda jest konstruktorem.

TomWolk
źródło