Ist es sicher, Werte aus einer java.util.HashMap von mehreren Threads abzurufen (keine Änderung)?

138

Es gibt einen Fall, in dem eine Karte erstellt wird und nach ihrer Initialisierung nie wieder geändert wird. Der Zugriff (jedoch nur über get (Schlüssel)) erfolgt über mehrere Threads. Ist es sicher, a java.util.HashMapauf diese Weise zu verwenden?

(Derzeit verwende ich gerne a java.util.concurrent.ConcurrentHashMapund habe kein gemessenes Bedürfnis, die Leistung zu verbessern, bin aber einfach neugierig, ob ein einfaches HashMapausreichen würde. Daher lautet diese Frage weder "Welches soll ich verwenden?" Noch eine Leistungsfrage. Die Frage ist vielmehr "Wäre es sicher?")

Dave L.
quelle
4
Viele Antworten hier sind in Bezug auf den gegenseitigen Ausschluss von laufenden Threads richtig, in Bezug auf Speicheraktualisierungen jedoch falsch. Ich habe entsprechend nach oben / unten gestimmt, aber es gibt immer noch viele falsche Antworten mit positiven Stimmen.
Heath Borders
@Heath Borders, wenn die Instanz a statisch initialisiert wurde und nicht modifizierbar ist, sollte sie für das gleichzeitige Lesen sicher sein (da andere Threads keine Updates verpasst haben konnten, da keine Updates vorhanden waren), oder?
Kaqqao
Wenn es statisch initialisiert und außerhalb des statischen Blocks nie geändert wird, ist es möglicherweise in Ordnung, da die gesamte statische Initialisierung durch das synchronisiert wird ClassLoader. Das ist eine eigene Frage wert. Ich würde es immer noch explizit synchronisieren und profilieren, um zu überprüfen, ob es echte Leistungsprobleme verursacht.
Heath Borders
@HeathBorders - was meinst du mit "Speicheraktualisierungen"? Die JVM ist ein formales Modell, das Dinge wie Sichtbarkeit, Atomizität und Vor-Ort- Beziehungen definiert, jedoch keine Begriffe wie "Speicheraktualisierungen" verwendet. Sie sollten dies klarstellen, vorzugsweise unter Verwendung der Terminologie aus dem JLS.
BeeOnRope
2
@ Dave - Ich gehe davon aus, dass Sie nach 8 Jahren immer noch keine Antwort suchen, aber für die Aufzeichnung besteht die Hauptverwirrung in fast allen Antworten darin, dass sie sich auf die Aktionen konzentrieren, die Sie für das Kartenobjekt ausführen . Sie haben bereits erklärt, dass Sie das Objekt niemals ändern, sodass dies alles irrelevant ist. Das einzige mögliche "Gotcha" ist dann, wie Sie den Verweis auf das veröffentlichen Map, was Sie nicht erklärt haben. Wenn Sie es nicht sicher machen, ist es nicht sicher. Wenn Sie es sicher tun, ist es . Details in meiner Antwort.
BeeOnRope

Antworten:

55

Ihr Idiom ist sicher , wenn und nur wenn der Verweis auf das HashMapist sicher veröffentlicht . Die sichere Veröffentlichung befasst sich nicht mit den Interna von sich HashMapselbst, sondern damit, wie der Konstruktions-Thread den Verweis auf die Karte für andere Threads sichtbar macht.

Grundsätzlich besteht hier der einzig mögliche Wettlauf zwischen dem Aufbau des HashMapund aller Lesethreads, die darauf zugreifen können, bevor er vollständig aufgebaut ist. In den meisten Diskussionen geht es darum, was mit dem Status des Kartenobjekts geschieht. Dies ist jedoch irrelevant, da Sie es nie ändern. Der einzig interessante Teil ist daher, wie die HashMapReferenz veröffentlicht wird.

Stellen Sie sich zum Beispiel vor, Sie veröffentlichen die Karte folgendermaßen:

class SomeClass {
   public static HashMap<Object, Object> MAP;

   public synchronized static setMap(HashMap<Object, Object> m) {
     MAP = m;
   }
}

... und wird irgendwann setMap()mit einer Karte aufgerufen, und andere Threads verwenden, SomeClass.MAPum auf die Karte zuzugreifen, und suchen wie folgt nach Null:

HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
  .. use the map
} else {
  .. some default behavior
}

Dies ist nicht sicher , obwohl es wahrscheinlich so aussieht, als ob es so wäre. Das Problem ist , dass es keine geschieht vor- Beziehung zwischen dem Satz SomeObject.MAPund der nachfolgenden Leseoperation auf einem anderen Thread, so dass der Lesefaden frei , um zu sehen eine teilweise konstruierten Karte ist. Dies kann so ziemlich alles und sogar in der Praxis Dinge tun , wie den Lesethread in eine Endlosschleife zu bringen .

Um sicher die Karte zu veröffentlichen, müssen Sie etablieren ein geschieht zuvor Beziehung zwischen dem Schreiben des Referenz auf die HashMap(dh der Veröffentlichung ) und die nachfolgenden Leser dieser Verweisung (dh der Verbrauch). Praktischerweise gibt es nur wenige leicht zu merkende Weise zu erreichen , dass [1] :

  1. Tauschen Sie die Referenz über ein ordnungsgemäß gesperrtes Feld aus ( JLS 17.4.5 ).
  2. Verwenden Sie den statischen Initialisierer, um die Speicher zu initialisieren ( JLS 12.4 ).
  3. Tauschen Sie die Referenz über ein flüchtiges Feld ( JLS 17.4.5 ) oder als Folge dieser Regel über die AtomicX-Klassen aus
  4. Initialisieren Sie den Wert in ein letztes Feld ( JLS 17.5 ).

Die für Ihr Szenario interessantesten sind (2), (3) und (4). Insbesondere gilt (3) direkt für den Code, den ich oben habe: Wenn Sie die Deklaration von MAPin transformieren :

public static volatile HashMap<Object, Object> MAP;

dann ist alles koscher: Leser , die einen sehen Nicht-Null - Wert unbedingt eine zuvor passiert Beziehung mit dem Laden MAPalle Geschäfte und damit mit der Karte Initialisierung zugeordnet sind .

Die anderen Methoden ändern die Semantik Ihrer Methode, da sowohl (2) (mit dem statischen Initializer) als auch (4) (mit final ) implizieren, dass Sie MAPzur Laufzeit nicht dynamisch festlegen können . Wenn Sie dies nicht tun müssen , deklarieren Sie es einfach MAPals static final HashMap<>und Sie erhalten eine sichere Veröffentlichung.

In der Praxis sind die Regeln für den sicheren Zugriff auf "nie geänderte Objekte" einfach:

Wenn Sie ein Objekt veröffentlichen, das nicht von Natur aus unveränderlich ist (wie in allen deklarierten Feldern final) und:

  • Sie können bereits das Objekt erstellen, das zum Zeitpunkt der Deklaration zugewiesen wird. A : Verwenden Sie einfach ein finalFeld (auch static finalfür statische Elemente).
  • Sie möchten das Objekt später zuordnen, nachdem die Referenz bereits sichtbar ist: ein flüchtiges Feld verwenden , b .

Das ist es!

In der Praxis ist es sehr effizient. Die Verwendung eines static finalFelds ermöglicht es der JVM beispielsweise, anzunehmen, dass der Wert für die Lebensdauer des Programms unverändert bleibt, und ihn stark zu optimieren. Die Verwendung eines finalMitgliedes Feld ermöglicht die meisten Architekturen , das Feld auf eine Weise äquivalent zu einem normalen Lesefeld zu lesen und nicht hemmt weitere Optimierungen c .

Schließlich hat die Verwendung von volatileeinige Auswirkungen: Auf vielen Architekturen (z. B. x86, insbesondere solchen, bei denen Lesevorgänge keine Lesevorgänge zulassen) ist keine Hardwarebarriere erforderlich, aber einige Optimierungen und Neuordnungen treten möglicherweise beim Kompilieren nicht auf - dies jedoch Effekt ist im Allgemeinen gering. Im Gegenzug erhalten Sie tatsächlich mehr als gewünscht HashMap- Sie können nicht nur eine sicher veröffentlichen , sondern auch so viele nicht geänderte HashMaps speichern, wie Sie möchten, und sicher sein, dass alle Leser eine sicher veröffentlichte Karte sehen .

Weitere Informationen finden Sie in Shipilev oder in diesen FAQ von Manson und Goetz .


[1] Direkt aus dem Schiff zitieren .


a Das klingt kompliziert, aber ich meine, Sie können die Referenz zur Konstruktionszeit zuweisen - entweder am Deklarationspunkt oder im Konstruktor (Elementfelder) oder statischen Initialisierer (statische Felder).

b Optional können Sie eine synchronizedMethode zum Abrufen / Festlegen oder eine AtomicReferenceoder etwas verwenden, aber wir sprechen über die Mindestarbeit, die Sie leisten können.

c Einige Architekturen mit sehr schwachen Speichermodellen (ich sehe Sie an , Alpha) erfordern möglicherweise eine Art Lesebarriere vor dem finalLesen - aber diese sind heute sehr selten.

BeeOnRope
quelle
never modify HashMapDas heißt nicht, dass das state of the map objectThread sicher ist, denke ich. Gott kennt die Bibliotheksimplementierung, wenn das offizielle Dokument nicht sagt, dass es threadsicher ist.
Jiang YD
@JiangYD - Sie haben Recht, in einigen Fällen gibt es dort eine Grauzone: Wenn wir "Ändern" sagen, meinen wir wirklich jede Aktion, die intern einige Schreibvorgänge ausführt , die mit Lese- oder Schreibvorgängen in anderen Threads konkurrieren könnten. Diese Schreibvorgänge können interne Implementierungsdetails sein, sodass selbst eine Operation, die "schreibgeschützt" zu sein scheint, get()tatsächlich einige Schreibvorgänge ausführt, z. B. das Aktualisieren einiger Statistiken (oder im Fall einer vom Zugriff geordneten LinkedHashMapAktualisierung der Zugriffsreihenfolge). Eine gut geschriebene Klasse sollte also eine Dokumentation liefern, die klar macht, ob ...
BeeOnRope
... anscheinend sind "schreibgeschützte" Operationen im Sinne der Thread-Sicherheit wirklich intern schreibgeschützt. In der C ++ - Standardbibliothek gibt es beispielsweise eine pauschale Regel, nach der markierte constElementfunktionen in einem solchen Sinne wirklich schreibgeschützt sind (intern können sie zwar noch Schreibvorgänge ausführen, diese müssen jedoch threadsicher gemacht werden). constIn Java gibt es kein Schlüsselwort, und mir ist keine dokumentierte Pauschalgarantie bekannt. Im Allgemeinen verhalten sich die Standardbibliotheksklassen jedoch wie erwartet, und Ausnahmen werden dokumentiert (siehe LinkedHashMapBeispiel, in dem RO-Operationen wie getexplizit als unsicher bezeichnet werden).
BeeOnRope
@JiangYD - zurück zu Ihrer ursprünglichen Frage, denn HashMapwir haben tatsächlich direkt in der Dokumentation das Thread-Sicherheitsverhalten für diese Klasse: Wenn mehrere Threads gleichzeitig auf eine Hash-Map zugreifen und mindestens einer der Threads die Map strukturell ändert, es muss extern synchronisiert werden. (Eine strukturelle Änderung ist eine Operation, die eine oder mehrere Zuordnungen hinzufügt oder löscht. Das bloße Ändern des Werts eines Schlüssels, den eine Instanz bereits enthält, ist keine strukturelle Änderung.)
BeeOnRope
Daher sind HashMapMethoden, von denen wir erwarten, dass sie schreibgeschützt sind, schreibgeschützt, da sie die nicht strukturell ändern HashMap. Natürlich gilt diese Garantie möglicherweise nicht für beliebige andere MapImplementierungen, aber die Frage betrifft HashMapspeziell.
BeeOnRope
70

Jeremy Manson, der Gott, wenn es um das Java-Speichermodell geht, hat einen dreiteiligen Blog zu diesem Thema - weil Sie im Wesentlichen die Frage "Ist es sicher, auf eine unveränderliche HashMap zuzugreifen" stellen - die Antwort darauf lautet ja. Aber Sie müssen das Prädikat auf die Frage beantworten: "Ist meine HashMap unveränderlich?". Die Antwort könnte Sie überraschen - Java verfügt über ein relativ kompliziertes Regelwerk zur Bestimmung der Unveränderlichkeit.

Weitere Informationen zum Thema finden Sie in Jeremys Blog-Beiträgen:

Teil 1 zur Unveränderlichkeit in Java: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Teil 2 zur Unveränderlichkeit in Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Teil 3 zur Unveränderlichkeit in Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html

Taylor Gautier
quelle
3
Es ist ein guter Punkt, aber ich verlasse mich auf die statische Initialisierung, bei der keine Referenzen entweichen, daher sollte es sicher sein.
Dave L.
5
Ich verstehe nicht, wie dies eine hoch bewertete Antwort (oder sogar eine Antwort) ist. Zum einen wird die Frage nicht einmal beantwortet , und es wird nicht das eine Schlüsselprinzip erwähnt, das darüber entscheidet, ob es sicher ist oder nicht: die sichere Veröffentlichung . Die "Antwort" läuft auf "es ist schwierig" hinaus und hier sind drei (komplexe) Links, die Sie lesen können.
BeeOnRope
Er beantwortet die Frage ganz am Ende des ersten Satzes. In Bezug auf die Beantwortung spricht er den Punkt an, dass Unveränderlichkeit (auf die im ersten Absatz der Frage hingewiesen wird) nicht einfach ist, zusammen mit wertvollen Ressourcen, die dieses Thema weiter erläutern. Die Punkte messen nicht, ob es sich um eine Antwort handelt, sondern ob die Antwort für andere "nützlich" war. Die Antwort, die akzeptiert wird, bedeutet, dass es die Antwort war, nach der das OP gesucht hat und die Ihre Antwort erhalten hat.
Jesse
@Jesse er beantwortet die Frage am Ende des ersten Satzes nicht, er beantwortet die Frage "Ist es sicher, auf ein unveränderliches Objekt zuzugreifen", die möglicherweise auf die Frage des OP zutrifft oder nicht, wie er im nächsten Satz ausführt. Im Wesentlichen handelt es sich hierbei um eine fast nur auf Links basierende Antwort vom Typ "Go Figure It Yourself", die für SO keine gute Antwort ist. Was die Upvotes betrifft, denke ich, dass es eher eine Funktion des Alters von 10,5 Jahren und ein häufig gesuchtes Thema ist. Es hat in den letzten Jahren nur sehr wenige Netto-Upvotes erhalten, also kommen vielleicht Leute vorbei :).
BeeOnRope
35

Die Lesevorgänge sind unter dem Gesichtspunkt der Synchronisation sicher, jedoch nicht unter dem Gesichtspunkt des Speichers. Dies wird von Java-Entwicklern, einschließlich hier auf Stackoverflow, weitgehend missverstanden. (Beachten Sie die Bewertung dieser Antwort als Beweis.)

Wenn andere Threads ausgeführt werden, wird möglicherweise keine aktualisierte Kopie der HashMap angezeigt, wenn aus dem aktuellen Thread kein Speicher geschrieben wird. Speicherschreibvorgänge erfolgen durch die Verwendung synchronisierter oder flüchtiger Schlüsselwörter oder durch die Verwendung einiger Java-Parallelitätskonstrukte.

Weitere Informationen finden Sie in Brian Goetz 'Artikel über das neue Java-Speichermodell .

Heidegrenzen
quelle
Entschuldigung für die doppelte Einreichung Heath, ich habe Ihre erst bemerkt, nachdem ich meine eingereicht habe. :)
Alexander
2
Ich bin nur froh, dass es hier andere Leute gibt, die die Memory-Effekte tatsächlich verstehen.
Heath Borders
1
Obwohl kein Thread das Objekt sehen wird, bevor es ordnungsgemäß initialisiert wurde, denke ich, dass dies in diesem Fall kein Problem darstellt.
Dave L.
1
Das hängt ganz davon ab, wie das Objekt initialisiert wird.
Bill Michell
1
Die Frage besagt, dass er nach der Initialisierung der HashMap nicht beabsichtigt, sie weiter zu aktualisieren. Von da an möchte er es nur noch als schreibgeschützte Datenstruktur verwenden. Ich denke, es wäre sicher, vorausgesetzt, die in seiner Karte gespeicherten Daten sind unveränderlich.
Binita Bharati
9

Nach einigem Hin und Her fand ich dies im Java-Dokument (Hervorhebung von mir):

Beachten Sie, dass diese Implementierung nicht synchronisiert ist. Wenn mehrere Threads gleichzeitig auf eine Hash-Map zugreifen und mindestens einer der Threads die Map strukturell ändert, muss sie extern synchronisiert werden. (Eine strukturelle Änderung ist eine Operation, die eine oder mehrere Zuordnungen hinzufügt oder löscht. Das bloße Ändern des Werts eines Schlüssels, den eine Instanz bereits enthält, ist keine strukturelle Änderung.)

Dies scheint zu implizieren, dass es sicher sein wird, vorausgesetzt, die Umkehrung der Aussage dort ist wahr.

Dave L.
quelle
1
Während dies ein ausgezeichneter Rat ist, wie andere Antworten besagen, gibt es eine differenziertere Antwort im Fall einer unveränderlichen, sicher veröffentlichten Karteninstanz. Aber Sie sollten das nur tun, wenn Sie wissen, was Sie tun.
Alex Miller
1
Hoffentlich können mehr von uns bei Fragen wie diesen wissen, was wir tun.
Dave L.
Das ist nicht wirklich richtig. Da die anderen Antworten Zustand, muss es ein geschieht zuvor zwischen der letzten Änderung und alle nachfolgenden „thread - safe“ liest. Normalerweise bedeutet dies, dass Sie das Objekt sicher veröffentlichen müssen, nachdem es erstellt und seine Änderungen vorgenommen wurden. Siehe die erste, markierte richtige Antwort.
Markspace
9

Ein Hinweis ist, dass unter bestimmten Umständen ein get () von einer nicht synchronisierten HashMap eine Endlosschleife verursachen kann. Dies kann auftreten, wenn ein gleichzeitiges put () ein erneutes Aufbereiten der Karte bewirkt.

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

Alex Miller
quelle
1
Eigentlich habe ich gesehen, dass dies die JVM hängen lässt, ohne CPU zu verbrauchen (was vielleicht schlimmer ist)
Peter Lawrey
2
Ich denke, dieser Code wurde so umgeschrieben, dass es nicht mehr möglich ist, die Endlosschleife zu erhalten. Aber Sie sollten aus anderen Gründen immer noch nicht versuchen, eine nicht synchronisierte HashMap abzurufen und zu platzieren.
Alex Miller
@AlexMiller Auch abgesehen von den anderen Gründen (ich nehme an, Sie beziehen sich auf sicheres Publizieren) sollte eine Implementierungsänderung meines Erachtens kein Grund sein, die Zugriffsbeschränkungen zu lockern, es sei denn, dies ist in der Dokumentation ausdrücklich vorgesehen. Zufällig enthält die HashMap Javadoc für Java 8 noch diese Warnung: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

Es gibt jedoch eine wichtige Wendung. Es ist sicher, auf die Karte zuzugreifen, aber im Allgemeinen kann nicht garantiert werden, dass alle Threads genau den gleichen Status (und damit die gleichen Werte) der HashMap sehen. Dies kann auf Multiprozessorsystemen passieren, bei denen die Änderungen an der HashMap, die von einem Thread (z. B. demjenigen, der sie gefüllt hat) vorgenommen wurden, im Cache dieser CPU gespeichert werden können und von Threads, die auf anderen CPUs ausgeführt werden, erst dann angezeigt werden, wenn eine Speicherzaunoperation ausgeführt wird durchgeführt, um die Cache-Kohärenz sicherzustellen. Die Java-Sprachspezifikation ist in diesem Fall explizit: Die Lösung besteht darin, eine Sperre (synchronisiert (...)) zu erwerben, die eine Speicherzaunoperation ausgibt. Wenn Sie also sicher sind, dass nach dem Auffüllen der HashMap jeder Thread eine beliebige Sperre erhält, ist es von diesem Punkt an in Ordnung, von einem beliebigen Thread aus auf die HashMap zuzugreifen, bis die HashMap erneut geändert wird.

Alexander
quelle
Ich bin nicht sicher, ob der Thread, der darauf zugreift, eine Sperre erhält, aber ich bin sicher, dass sie erst nach der Initialisierung einen Verweis auf das Objekt erhalten, sodass ich nicht glaube, dass sie eine veraltete Kopie haben können.
Dave L.
@Alex: Der Verweis auf die HashMap kann flüchtig sein, um die gleichen Garantien für die Speichersichtbarkeit zu erzielen. @ Dave: Es ist möglich, Verweise auf neue Objekte anzuzeigen, bevor die Arbeit des Ctors für Ihren Thread sichtbar wird.
Chris Vest
@ Christian Im allgemeinen sicherlich. Ich habe gesagt, dass dies in diesem Code nicht der Fall ist.
Dave L.
Durch den Erwerb einer RANDOM-Sperre wird nicht sichergestellt, dass der gesamte Thread-CPU-Cache gelöscht wird. Dies hängt von der JVM-Implementierung ab und wird höchstwahrscheinlich nicht auf diese Weise durchgeführt.
Pierre
Ich stimme Pierre zu, ich denke nicht, dass es ausreichen wird, ein Schloss zu erwerben. Sie müssen auf derselben Sperre synchronisieren, damit die Änderungen sichtbar werden.
Damluar
5

Laut http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Initialisierungssicherheit können Sie Ihre HashMap zu einem endgültigen Feld machen und sie nach Abschluss des Konstruktors sicher veröffentlichen.

... Unter dem neuen Speichermodell gibt es etwas Ähnliches wie eine Vorher-Beziehung zwischen dem Schreiben eines letzten Felds in einem Konstruktor und dem anfänglichen Laden eines gemeinsamen Verweises auf dieses Objekt in einem anderen Thread. ...

Bodrin
quelle
Diese Antwort ist von geringer Qualität. Sie entspricht der Antwort von @taylor gauthier, enthält jedoch weniger Details.
Snicolas
1
Ummmm ... kein Arsch zu sein, aber du hast es rückwärts. Taylor sagte: "Nein, schau dir diesen Blog-Beitrag an, die Antwort könnte dich überraschen.", Während diese Antwort tatsächlich etwas Neues hinzufügt, das ich nicht wusste ... Über eine Beziehung zwischen dem Schreiben eines letzten Feldes in einem Konstrukteur. Diese Antwort ist ausgezeichnet und ich bin froh, dass ich sie gelesen habe.
Ajax
Huh? Dies ist die einzig richtige Antwort, die ich gefunden habe, nachdem ich durch die höher bewerteten Antworten gescrollt habe. Der Schlüssel wird sicher veröffentlicht und dies ist die einzige Antwort, die ihn überhaupt erwähnt.
BeeOnRope
3

Das von Ihnen beschriebene Szenario besteht also darin, dass Sie eine Reihe von Daten in eine Karte einfügen müssen. Wenn Sie mit dem Auffüllen fertig sind, behandeln Sie sie als unveränderlich. Ein Ansatz, der "sicher" ist (dh Sie erzwingen, dass er wirklich als unveränderlich behandelt wird), besteht darin, die Referenz durch zu ersetzen, Collections.unmodifiableMap(originalMap)wenn Sie bereit sind, sie unveränderlich zu machen.

Ein Beispiel dafür, wie schlecht Karten bei gleichzeitiger Verwendung fehlschlagen können, und die von mir erwähnte vorgeschlagene Problemumgehung finden Sie in diesem Eintrag zur Fehlerparade : bug_id = 6423457

Wille
quelle
2
Dies ist insofern "sicher", als es die Unveränderlichkeit erzwingt, aber das Thread-Sicherheitsproblem nicht anspricht. Wenn der Zugriff auf die Karte mit dem UnmodifyingMap-Wrapper sicher ist, ist sie ohne sie sicher und umgekehrt.
Dave L.
2

Diese Frage wird in Brian Goetz 'Buch "Java Concurrency in Practice" (Listing 16.8, Seite 350) behandelt:

@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);
    }
}

Da statesals deklariert wird finalund seine Initialisierung im Klassenkonstruktor des Besitzers erfolgt, wird jedem Thread, der diese Map später liest, garantiert, dass sie zum Zeitpunkt des Abschlusses des Konstruktors angezeigt wird, vorausgesetzt, kein anderer Thread versucht, den Inhalt der Map zu ändern.

escudero380
quelle
1

Seien Sie gewarnt, dass das Ersetzen einer ConcurrentHashMap durch eine HashMap selbst in Single-Thread-Code möglicherweise nicht sicher ist. ConcurrentHashMap verbietet null als Schlüssel oder Wert. HashMap verbietet sie nicht (nicht fragen).

In der unwahrscheinlichen Situation, dass Ihr vorhandener Code der Sammlung während des Setups eine Null hinzufügt (vermutlich in einem Fehlerfall), ändert das Ersetzen der Sammlung wie beschrieben das Funktionsverhalten.

Vorausgesetzt, Sie tun nichts anderes, sind gleichzeitige Lesevorgänge von einer HashMap sicher.

[Bearbeiten: Mit "gleichzeitigen Lesevorgängen" meine ich, dass es nicht auch gleichzeitige Änderungen gibt.

Andere Antworten erklären, wie dies sichergestellt werden kann. Eine Möglichkeit besteht darin, die Karte unveränderlich zu machen, dies ist jedoch nicht erforderlich. Beispielsweise definiert das JSR133-Speichermodell das Starten eines Threads explizit als synchronisierte Aktion, was bedeutet, dass Änderungen, die in Thread A vor dem Start von Thread B vorgenommen wurden, in Thread B sichtbar sind.

Ich möchte diesen detaillierteren Antworten zum Java-Speichermodell nicht widersprechen. Diese Antwort soll darauf hinweisen, dass es neben Nebenläufigkeitsproblemen mindestens einen API-Unterschied zwischen ConcurrentHashMap und HashMap gibt, der sogar ein Single-Thread-Programm, das eines durch das andere ersetzt, zunichte machen könnte.]

Steve Jessop
quelle
Vielen Dank für die Warnung, aber es gibt keine Versuche, Nullschlüssel oder Werte zu verwenden.
Dave L.
Ich dachte, es würde nicht geben. Nullen in Sammlungen sind eine verrückte Ecke von Java.
Steve Jessop
Ich stimme dieser Antwort nicht zu. "Gleichzeitige Lesevorgänge von einer HashMap sind sicher" ist an sich falsch. Es wird nicht angegeben, ob die Lesevorgänge für eine Karte erfolgen, die veränderlich oder unveränderlich ist. Um richtig zu sein, sollte es lauten "Gleichzeitige Lesevorgänge von einer unveränderlichen HashMap sind sicher"
Taylor Gautier
2
Nicht gemäß den Artikeln, mit denen Sie selbst verlinkt haben: Die Anforderung ist, dass die Karte nicht geändert werden darf (und frühere Änderungen für alle Leserthreads sichtbar sein müssen), nicht, dass sie unveränderlich ist (was ein technischer Begriff in Java ist und a ausreichende, aber nicht notwendige Sicherheitsbedingung).
Steve Jessop
Auch ein Hinweis ... Das Initialisieren einer Klasse wird implizit mit derselben Sperre synchronisiert (ja, Sie können in statischen Feldinitialisierern einen Deadlock durchführen). Wenn Ihre Initialisierung also statisch erfolgt, kann sie niemand anderes sehen, bevor die Initialisierung abgeschlossen ist Sie müssten in der ClassLoader.loadClass-Methode für dieselbe erworbene Sperre blockiert werden ... Und wenn Sie sich über verschiedene Klassenladeprogramme mit unterschiedlichen Kopien desselben Felds wundern, wären Sie korrekt ... aber das wäre orthogonal zu Vorstellung von Rennbedingungen; statische Felder eines Klassenladeprogramms teilen sich einen Speicherzaun.
Ajax
0

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

Hier ist die Quelle für HashMap. Wie Sie sehen, gibt es dort absolut keinen Sperr- / Mutex-Code.

Dies bedeutet, dass es zwar in einer Multithread-Situation in Ordnung ist, aus einer HashMap zu lesen, ich aber definitiv eine ConcurrentHashMap verwenden würde, wenn es mehrere Schreibvorgänge gäbe.

Interessant ist, dass sowohl .NET HashTable als auch Dictionary <K, V> Synchronisationscode integriert haben.

FlySwat
quelle
2
Ich denke, es gibt Klassen, in denen das einfache gleichzeitige Lesen Sie in Schwierigkeiten bringen kann, beispielsweise aufgrund der internen Verwendung temporärer Instanzvariablen. Man muss also wahrscheinlich die Quelle sorgfältig untersuchen, mehr als einen schnellen Scan nach Sperr- / Mutex-Code.
Dave L.
0

Wenn die Initialisierung und jeder Put synchronisiert sind, sind Sie gespeichert.

Der folgende Code wird gespeichert, da sich der Klassenladeprogramm um die Synchronisierung kümmert:

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

}

Der folgende Code wird gespeichert, da das Schreiben von flüchtig die Synchronisation übernimmt.

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;
  }
}

Dies funktioniert auch, wenn die Mitgliedsvariable final ist, da final ebenfalls volatil ist. Und wenn die Methode ein Konstruktor ist.

TomWolk
quelle