Ist der folgende Code so eingerichtet, dass die Anrufe korrekt synchronisiert werden synchronizedMap
?
public class MyClass {
private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());
public void doWork(String key) {
List<String> values = null;
while ((values = synchronizedMap.remove(key)) != null) {
//do something with values
}
}
public static void addToMap(String key, String value) {
synchronized (synchronizedMap) {
if (synchronizedMap.containsKey(key)) {
synchronizedMap.get(key).add(value);
}
else {
List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, valuesList);
}
}
}
}
Nach meinem Verständnis benötige ich den synchronisierten Block addToMap()
, um zu verhindern, dass ein anderer Thread aufruft remove()
oder containsKey()
bevor ich den Aufruf erhalte, put()
aber ich benötige keinen synchronisierten Block, doWork()
da ein anderer Thread den synchronisierten Block addToMap()
vor der remove()
Rückkehr nicht eingeben kann, da ich die Map ursprünglich erstellt habe mit Collections.synchronizedMap()
. Ist das korrekt? Gibt es einen besseren Weg, dies zu tun?
quelle
Collections.synchronizedMap()
? Ich verstehe den zweiten Punkt nicht.Wenn Sie JDK 6 verwenden, sollten Sie ConcurrentHashMap ausprobieren
Beachten Sie die putIfAbsent-Methode in dieser Klasse.
quelle
Es besteht die Möglichkeit eines subtilen Fehlers in Ihrem Code.
[ UPDATE: Da er map.remove () verwendet, ist diese Beschreibung nicht vollständig gültig. Ich habe diese Tatsache beim ersten Mal verpasst. :( Vielen Dank an den Autor der Frage, der darauf hingewiesen hat. Ich lasse den Rest unverändert, habe aber die Lead-Anweisung dahingehend geändert, dass möglicherweise ein Fehler vorliegt.]
In doWork () erhalten Sie den Listenwert threadsicher aus der Map. Danach greifen Sie jedoch in einer unsicheren Angelegenheit auf diese Liste zu. Beispielsweise kann ein Thread die Liste in doWork () verwenden, während ein anderer Thread synchronizedMap.get (Schlüssel) .add (Wert) in addToMap () aufruft . Diese beiden Zugriffe sind nicht synchronisiert. Als Faustregel gilt, dass sich die thread-sicheren Garantien einer Sammlung nicht auf die von ihnen gespeicherten Schlüssel oder Werte erstrecken.
Sie können dies beheben, indem Sie eine synchronisierte Liste wie in die Karte einfügen
Alternativ können Sie auf der Karte synchronisieren, während Sie auf die Liste in doWork () zugreifen :
Die letzte Option wird die Parallelität ein wenig einschränken, ist aber IMO etwas klarer.
Außerdem ein kurzer Hinweis zu ConcurrentHashMap. Dies ist eine wirklich nützliche Klasse, aber nicht immer ein geeigneter Ersatz für synchronisierte HashMaps. Zitat aus seinen Javadocs,
Mit anderen Worten, putIfAbsent () eignet sich hervorragend für atomare Einfügungen, garantiert jedoch nicht, dass sich andere Teile der Karte während dieses Aufrufs nicht ändern. es garantiert nur Atomizität. In Ihrem Beispielprogramm verlassen Sie sich bei anderen Dingen als put () auf die Synchronisationsdetails von (einer synchronisierten) HashMap.
Letztes Ding. :) Dieses großartige Zitat aus Java Concurrency in der Praxis hilft mir immer beim Entwerfen eines Debugging-Multithread-Programms.
quelle
Ja, Sie synchronisieren richtig. Ich werde dies genauer erklären. Sie müssen zwei oder mehr Methodenaufrufe für das synchronizedMap-Objekt nur dann synchronisieren, wenn Sie sich auf die Ergebnisse vorheriger Methodenaufrufe im nachfolgenden Methodenaufruf in der Reihenfolge der Methodenaufrufe für das synchronizedMap-Objekt verlassen müssen. Schauen wir uns diesen Code an:
In diesem Code
und
Methodenaufrufe basieren auf dem Ergebnis des vorherigen
Methodenaufruf.
Wenn die Reihenfolge der Methodenaufrufe nicht synchronisiert wurde, ist das Ergebnis möglicherweise falsch. Beispiel
thread 1
: Ausführen der MethodeaddToMap()
undthread 2
Ausführen der MethodedoWork()
Die Reihenfolge der Methodenaufrufe für dassynchronizedMap
Objekt kann wie folgt sein:Thread 1
hat die Methode ausgeführtund das Ergebnis ist "
true
". Nachdem dieses Betriebssystem die Ausführungssteuerung auf umgeschaltetthread 2
und ausgeführt hatDanach wurde die Ausführungssteuerung wieder auf die umgeschaltet
thread 1
und zum Beispiel ausgeführtzu glauben, dass das
synchronizedMap
Objekt das enthältkey
undNullPointerException
geworfen wird, weilsynchronizedMap.get(key)
es zurückkehren wirdnull
. Wenn die Reihenfolge der Methodenaufrufe für dassynchronizedMap
Objekt nicht von den Ergebnissen der anderen abhängig ist, müssen Sie die Reihenfolge nicht synchronisieren. Zum Beispiel müssen Sie diese Sequenz nicht synchronisieren:Hier
Der Methodenaufruf basiert nicht auf den Ergebnissen des vorherigen
Methodenaufruf (es ist egal, ob ein Thread zwischen den beiden Methodenaufrufen eingegriffen und beispielsweise den entfernt hat
key1
).quelle
Das sieht für mich richtig aus. Wenn ich etwas ändern würde, würde ich die Verwendung von Collections.synchronizedMap () einstellen und alles auf die gleiche Weise synchronisieren, um es klarer zu machen.
Auch würde ich ersetzen
mit
quelle
Collections.synchronizedXXX()
APIs verwenden sollten, wenn wir noch ein Objekt (in den meisten Fällen nur die Sammlung selbst) in der Logik unserer täglichen AppDie Art und Weise, wie Sie synchronisiert haben, ist korrekt. Aber es gibt einen Haken
In der realen Welt würden Sie jedoch im Allgemeinen die Karte abfragen, bevor Sie den Wert eingeben. Daher müssten Sie zwei Operationen ausführen, und daher wird ein synchronisierter Block benötigt. Die Art und Weise, wie Sie es verwendet haben, ist also korrekt. Jedoch.
ein. Es hat eine API 'putIfAbsent', die das gleiche tun würde, aber effizienter.
b. Es ist effizient: dDie CocurrentMap sperrt nur die Schlüssel, wodurch nicht die gesamte Welt der Karte blockiert wird. Wo Sie Schlüssel sowie Werte gesperrt haben.
c. Möglicherweise haben Sie die Referenz Ihres Kartenobjekts an einer anderen Stelle in Ihrer Codebasis übergeben, wo Sie / andere Entwickler in Ihrer Tean sie möglicherweise falsch verwenden. Das heißt, er kann einfach alle hinzufügen () oder erhalten (), ohne das Objekt der Karte zu sperren. Daher schließt sich sein Aufruf nicht gegenseitig für Ihren Synchronisierungsblock aus. Die Verwendung einer gleichzeitigen Implementierung gibt Ihnen jedoch die Gewissheit, dass sie niemals falsch verwendet / implementiert werden kann.
quelle
Schauen Sie sich Google Collections an
Multimap
, z. B. Seite 28 dieser Präsentation .Wenn Sie diese Bibliothek aus irgendeinem Grund nicht verwenden können, sollten Sie sie
ConcurrentHashMap
anstelle von verwendenSynchronizedHashMap
. Es gibt eine raffinierteputIfAbsent(K,V)
Methode, mit der Sie die Elementliste atomar hinzufügen können, wenn sie noch nicht vorhanden ist. Erwägen Sie auch die VerwendungCopyOnWriteArrayList
für die Kartenwerte, wenn Ihre Verwendungsmuster dies rechtfertigen.quelle