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

StackOverflow https://stackoverflow.com/questions/104184

Frage

Es gibt einen Fall, in dem eine Karte gebaut werden, und sobald es initialisiert wird, wird es nie wieder verändert werden. Es wird jedoch die zugegriffen werden soll (über get (key) nur) von mehreren Threads. Ist es sicher, ein java.util.HashMap auf diese Weise zu benutzen?

(Zur Zeit verwende ich gerne ein java.util.concurrent.ConcurrentHashMap und habe keine Notwendigkeit gemessene Leistung zu verbessern, aber ich bin einfach neugierig, ob ein einfaches HashMap ausreichen würde. Daher ist diese Frage nicht „Welches verwenden sollte ich?“, noch ist es eine Frage Leistung. Vielmehr ist die Frage:‚Wäre es sicher?‘)

War es hilfreich?

Lösung

Ihr Idiom ist sicher , wenn und nur wenn der Verweis auf die HashMap ist sicher veröffentlicht . Anstatt etwas die Interna von HashMap beziehen sich, sichere Veröffentlichung behandelt, wie das Konstruieren Faden den Verweis auf die Karte sichtbar zu anderen Threads macht.

Im Grunde genommen hier die einzig möglichen Rennen zwischen dem Aufbau des HashMap und aller Lese Threads, die es zugreifen können, bevor es vollständig aufgebaut ist. Der größte Teil der Diskussion über das, was passiert mit dem Zustand des Map-Objekt, aber das ist irrelevant, da Sie es nie ändern - so der einzige interessante Teil ist, wie die HashMap Referenz veröffentlicht wird.

Zum Beispiel, stellen Sie sich die Karte wie folgt veröffentlichen:

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

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

... und irgendwann setMap() ist mit einer Karte genannt, und andere Threads verwenden SomeClass.MAP die Karte zugreifen zu können, und überprüfen Sie auf Null wie folgt aus:

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

Dies ist nicht sicher , obwohl es scheint, als wahrscheinlich, obwohl es ist. Das Problem ist, dass es kein passiert-vor Beziehung zwischen dem Satz von SomeObject.MAP und dem nachfolgenden lesen auf einem anderen Thread, so den Lesefaden frei ist eine teilweise konstruierten Karte zu sehen. Dies kann so ziemlich tun alles und auch in der Praxis funktioniert es Dinge wie setzt den Lesefaden in eine Endlosschleife .

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

  1. Tauschen Sie die Referenz durch eine ordnungsgemäß verriegelt Feld ( JLS 17.4.5 )
  2. Verwenden Sie statische Initialisierer die Initialisierung speichert ( JLS 12.4 )
  3. Tauschen Sie den Verweis über ein flüchtiges Feld ( JLS 17.4.5 ) oder als Folge dieser Regel über die AtomicX Klassen
  4. Initialisieren Sie den Wert in einem letzten Feld ( Shipilev oder diese FAQ von Manson und Goetz .


    [1] direkt unter Angabe von shipilev .


    a Das klingt kompliziert, aber was ich meine ist, dass Sie die Referenz auf Bauzeiten zuweisen können - entweder an der Deklarationsstelle oder im Konstruktor (Mitglied Feldern) oder statische Initialisierer (statische Felder) .

    b Optional können Sie eine synchronized Methode / Set zu bekommen, oder eine AtomicReference oder so etwas, aber wir reden über die minimale Arbeit, die Sie tun können.

    c Einige Architekturen mit sehr schwachen Speichermodellen (Ich betrachte Sie , Alpha) können irgendeine Art von Lesebarriere erfordern, bevor ein final lesen - aber diese sind heute sehr selten

Andere Tipps

Jeremy Manson, der Gott, wenn es um die Java-Speichermodell kommt, hat einen dreiteiligen Blog zu diesem Thema - denn im Grunde Sie die Frage stellen: „Ist es sicher eine unveränderliche HashMap zuzugreifen“ - die Antwort auf diese Frage ist Ja. Aber Sie müssen das Prädikat auf diese Frage beantworten, die ist - „Ist mein HashMap unveränderlich“. Die Antwort mag Sie überraschen -. Java einen relativ komplizierten Satz von Regeln hat Unveränderlichkeit zu bestimmen

Für weitere Informationen über das Thema, lesen Jeremy Blog-Beiträge:

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

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

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

Das liest sind aus einem Synchronisations Standpunkt sicher, aber keinen Speicher Standpunkt. Dies ist etwas, das unter Java-Entwicklern weit mißverstanden einschließlich hier auf Stackoverflow. (Beachten Sie die Bewertung von diese Antwort Beweis.)

Wenn Sie andere Threads ausgeführt haben, können sie nicht eine aktualisierte Kopie des HashMap sehen, ob es keine Speicher-Schreib aus dem aktuellen Thread. Speicher schreiben auftreten durch die Verwendung der synchronisierten oder flüchtige Keywords oder durch Verwendung von einigen Java Concurrency-Konstrukten.

Siehe Brian Goetz Artikel über das neue Java-Speichermodell für Details.

Nach etwas mehr suchen, fand ich diese in der java doc (Hervorhebung von mir):

  

Beachten Sie, dass diese Implementierung nicht   synchronisiert. Wenn mehrere Threads   Zugriff auf eine Hash-Karte gleichzeitig, und   mindestens eines der Fäden modifiziert die   Karte strukturell, muss es sein   extern synchronisiert. (A Struktur   Modifikation ist jede Operation, die   Zu oder eine oder mehr Zuordnungen löscht;   lediglich ein Ändern des Werts assoziiert   mit einem Schlüssel, dass eine Instanz bereits   enthält, ist keine strukturelle   Änderung.)

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

Ein Hinweis ist, dass unter bestimmten Umständen ein get () von einem unsynchronisierten HashMap kann zu einer Endlosschleife führen. Dies kann auftreten, wenn eine gleichzeitige put () einen Aufguss der Karte verursacht.

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

Es gibt eine wichtige Wendung though. Es ist sicher die Karte zugreifen, aber im Allgemeinen ist es nicht garantiert, dass alle Fäden exakt den gleichen Zustand sehen (und somit Werte) des HashMap. Dies könnte auf Multiprozessorsystemen vorkommen, wo die Modifikationen die HashMap von einem Thread durchgeführt (zum Beispiel diejenige, die es bevölkert) in diesem CPU-Cache sitzen kann und nicht durch Gewinde auf anderem CPUs zu erkennen ausgeführt wird, bis ein Speicher fence Betrieb Gewährleistung Cache-Kohärenz durchgeführt. Die Java Language Specification ist explizit auf diese: die Lösung ist eine Sperre (synchronisiert (...)) zu erwerben, die einen Speicher Zaun Betrieb emittiert. Also, wenn Sie sicher sind, dass die HashMap jeder der Fäden nach dem Bestücken ANY Sperre erwirbt, dann ist es OK von diesem Zeitpunkt an die HashMap von jedem Thread zuzugreifen, bis die HashMap wieder geändert werden.

Nach http://www.ibm.com/developerworks/java / Bibliothek / j-jtp03304 / # Initialisierung Sicherheit können Sie Ihre HashMap ein letztes Feld und nach dem Konstruktor beendet es würde sicher veröffentlicht.

machen

... Nach dem neuen Speichermodell, es ist etwas ähnlich zu einem Fall vor-Beziehung zwischen dem Schreiben eines Endfeld in einem Konstruktor und die Anfangslast von einer gemeinsamen Referenz auf dieses Objekt in einem anderen Thread. ...

So das Szenario, das Sie beschrieben ist, dass Sie eine Reihe von Daten in eine Karte setzen müssen, dann, wenn Sie es fertig sind bevölkern Sie es als unveränderlich zu behandeln. Ein Ansatz, der „sicher“ ist (was bedeutet, Sie erzwingen, dass es wirklich ist als unveränderlich behandelt) ist die Referenz mit Collections.unmodifiableMap(originalMap) zu ersetzen, wenn Sie bereit sind, es unveränderlich zu machen.

Für ein Beispiel dafür, wie schlecht Karten fehlschlagen kann, wenn gleichzeitig verwendet werden, und die vorgeschlagene Abhilfe, die ich erwähnte, lesen Sie in diesen Bug Parade Eintrag: bug_id = 6423457

Seien Sie gewarnt, dass auch in Single-Threaded-Code, kann nicht sicher eine ConcurrentHashMap mit einem HashMap zu ersetzen. ConcurrentHashMap verbietet als Schlüssel oder Wert null. HashMap verbietet sie nicht (nicht fragen).

So in der unwahrscheinlichen Situation, die Ihr vorhandener Code könnte eine Null in die Sammlung während der Installation hinzufügen (vermutlich in einem Fehlerfall irgendeiner Art), ersetzt die Sammlung, wie das funktionale Verhalten ändern.

Das heißt, sofern Sie nichts anderes tun, gleichzeitig von einem HashMap liest sicher sind.

[Edit:. Durch "concurrent liest", meine ich, dass es nicht auch gleichzeitig Änderungen

Andere Antworten erklären, wie dies zu gewährleisten. Eine Möglichkeit ist, die Karte unveränderlich zu machen, aber es ist nicht notwendig. Zum Beispiel definiert das JSR133 Speichermodell ausgehend explizit einen Thread eine synchronisierte Aktion zu sein, was bedeutet, dass in Thread A vorgenommene Änderungen, bevor es beginnt Faden B in Thread B sichtbar ist.

Meine Absicht ist es nicht, diese detailliertere Antworten zu dem Java-Speichermodell zu widersprechen. Diese Antwort soll darauf hinweisen, dass auch abgesehen von Parallelitätsproblemen, mindestens eine API Unterschied zwischen ConcurrentHashMap und HashMap ist, die auch ein Single-Threaded-Programm versenken könnte, die eine mit dem anderen ersetzt.]

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

Hier ist die Quelle für HashMap. Wie Sie sehen können, gibt es absolut keine Verriegelungs- / Mutex-Code gibt.

Das bedeutet, dass während seines von einem HashMap in einer Multi-Thread-Situation zu lesen okay, ich auf jeden Fall eine ConcurrentHashMap verwenden würde, wenn mehrere Schreib sind.

Was interessant ist, dass sowohl die .NET HashTable und Dictionary in Synchronisationscode aufgebaut haben.

Wenn die Initialisierung und jeder Put synchronisiert Sie speichern.

Code Es folgt speichern, da die Klassenlader Pflege der Synchronisation stattfinden wird:

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

}

Code Es folgt sparen, weil das Schreiben von flüchtiger Pflege der Synchronisation stattfinden wird.

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 Membervariable ist endgültig, da final auch flüchtig ist. Und wenn die Methode ein Konstruktor ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top