167 Stimmen

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

Es gibt einen Fall, in dem eine Karte erstellt wird, die nach ihrer Initialisierung nie wieder geändert wird. Es wird jedoch (nur über get(key)) von mehreren Threads aus auf sie zugegriffen werden. Ist es sicher, eine java.util.HashMap auf diese Weise?

(Derzeit verwende ich zum Glück eine java.util.concurrent.ConcurrentHashMap und habe keinen gemessenen Bedarf, die Leistung zu verbessern, sondern bin einfach neugierig, ob eine einfache HashMap ausreichen würde. Daher ist diese Frage no Es geht weder um die Frage "Welches soll ich verwenden?" noch um die Leistung. Die Frage lautet vielmehr: "Wäre es sicher?")

4 Stimmen

Viele Antworten hier sind richtig in Bezug auf den gegenseitigen Ausschluss von laufenden Threads, aber falsch in Bezug auf Speicheraktualisierungen. Ich habe entsprechend hoch/runter gestimmt, aber es gibt immer noch viele falsche Antworten mit positiven Stimmen.

0 Stimmen

@Heath Borders, wenn die Instanz a statisch initialisiert wurde unmodifiable HashMap, sollte es sicher für gleichzeitiges Lesen sein (wie andere Threads nicht Updates verpasst haben könnte, da es keine Updates), richtig?

0 Stimmen

Wenn es statisch initialisiert und nie außerhalb des statischen Blocks geändert wird, dann könnte es in Ordnung sein, weil alle statischen Initialisierungen durch die Synchronisierung der ClassLoader . Das ist schon eine eigene Frage wert. Ich würde es trotzdem explizit synchronisieren und ein Profil erstellen, um zu überprüfen, ob es wirklich Leistungsprobleme verursacht.

5voto

escudero380 Punkte 397

Diese Frage wird in dem Buch "Java Concurrency in Practice" von Brian Goetz behandelt (Listing 16.8, Seite 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);
    }
}

Desde states wird deklariert als final und ihre Initialisierung im Klassenkonstruktor des Eigentümers durchgeführt wird, ist jeder Thread, der diese Karte später liest, garantiert in der Lage, sie zum Zeitpunkt der Beendigung des Konstruktors zu sehen, vorausgesetzt, kein anderer Thread versucht, den Inhalt der Karte zu ändern.

2voto

Will Punkte 21

Das von Ihnen beschriebene Szenario besteht also darin, dass Sie eine Reihe von Daten in eine Map eingeben müssen, die Sie dann, wenn Sie mit dem Auffüllen fertig sind, als unveränderlich behandeln. Ein Ansatz, der "sicher" ist (was bedeutet, dass Sie erzwingen, dass es wirklich als unveränderlich behandelt wird) ist, den Verweis mit Collections.unmodifiableMap(originalMap) wenn Sie bereit sind, sie unveränderlich zu machen.

Ein Beispiel dafür, wie sehr Maps bei gleichzeitiger Verwendung scheitern können, und die vorgeschlagene Abhilfe, die ich erwähnt habe, finden Sie in diesem Bugparade-Eintrag: bug_id=6423457

2 Stimmen

Dies ist insofern "sicher", als es die Unveränderbarkeit erzwingt, aber es löst nicht das Problem der Threadsicherheit. Wenn der Zugriff auf die Map mit dem UnmodifiableMap-Wrapper sicher ist, dann ist er auch ohne ihn sicher und umgekehrt.

1voto

Steve Jessop Punkte 264569

Seien Sie gewarnt, dass auch in Single-Thread-Code, eine ConcurrentHashMap mit einer HashMap zu ersetzen möglicherweise nicht sicher sein. ConcurrentHashMap verbietet null als Schlüssel oder Wert. HashMap verbietet sie nicht (fragen Sie nicht).

In der unwahrscheinlichen Situation, dass Ihr vorhandener Code der Sammlung während der Einrichtung eine Null hinzufügt (vermutlich in einem Fehlerfall irgendeiner Art), wird das Ersetzen der Sammlung wie beschrieben das funktionale Verhalten ändern.

Das heißt, sofern Sie nichts anderes tun, sind gleichzeitige Lesevorgänge aus einer HashMap sicher.

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

Andere Antworten erläutern, wie dies sichergestellt werden kann. Eine Möglichkeit ist, die Map unveränderlich zu machen, aber das ist nicht notwendig. Das JSR133-Speichermodell definiert beispielsweise das Starten eines Threads ausdrücklich als synchronisierte Aktion, was bedeutet, dass Änderungen, die in Thread A vorgenommen werden, bevor dieser Thread B startet, in Thread B sichtbar sind.

Meine Absicht ist es nicht, diesen ausführlicheren Antworten über das Java-Speichermodell zu widersprechen. Diese Antwort soll darauf hinweisen, dass es, abgesehen von Gleichzeitigkeitsproblemen, mindestens einen API-Unterschied zwischen ConcurrentHashMap und HashMap gibt, der sogar ein Single-Thread-Programm zum Scheitern bringen könnte, das das eine durch das andere ersetzt].

0 Stimmen

Danke für die Warnung, aber es gibt keine Versuche, Nullschlüssel oder -werte zu verwenden.

0 Stimmen

Ich dachte, das gäbe es nicht. Nullen in Sammlungen sind eine verrückte Ecke von Java.

0 Stimmen

Ich bin mit dieser Antwort nicht einverstanden. "Gleichzeitige Lesevorgänge aus einer HashMap sind sicher" ist an sich schon falsch. Es wird nicht angegeben, ob die Lesevorgänge gegen eine veränderbare oder unveränderbare Map erfolgen. Korrekt müsste es heißen: "Concurrent reads from an immutable HashMap are safe".

0voto

FlySwat Punkte 165766

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 Sperren / Mutex-Code dort.

Dies bedeutet, dass während seine okay, um von einer HashMap in einer Multithreading-Situation zu lesen, ich würde definitiv eine ConcurrentHashMap verwenden, wenn es mehrere Schreibvorgänge waren.

Interessant ist, dass sowohl die .NET HashTable als auch das Dictionary<K,V> einen eingebauten Synchronisationscode haben.

2 Stimmen

Ich denke, es gibt Klassen, bei denen das einfache gleichzeitige Lesen zu Problemen führen kann, z. B. wegen der internen Verwendung von temporären Instanzvariablen. Man muss also wahrscheinlich den Quelltext sorgfältig untersuchen, mehr als nur einen schnellen Scan nach Sperr-/Mutex-Code.

0voto

TomWolk Punkte 908

Wenn die Initialisierung und jedes Put synchronisiert sind, sind Sie sicher.

Der folgende Code ist sicher, weil der Classloader sich um die Synchronisierung kümmern wird:

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

}

Der folgende Code ist sicher, weil das Schreiben von volatile die Synchronisierung ü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 auch flüchtig ist. Und wenn die Methode ein Konstruktor ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X