Ich möchte beim Entfernen von Einträgen regelmäßig über ein ConcurrentHashMap
iterieren:
Das Problem ist, dass ein anderer Thread während der Iteration Werte aktualisiert oder ändert. Wenn das passiert, können diese Updates für immer verloren gehen, weil mein Thread während des Iterierens nur veraltete Werte sieht, aber der remove()
wird den Live-Eintrag löschen.
Nach einiger Überlegung habe ich diese Problemumgehung gefunden:
%Vor% Ein Problem dabei ist, dass es keine Änderungen an änderbaren Werten abfängt, die equals()
nicht implementieren (z. B. AtomicInteger
). Gibt es eine bessere Möglichkeit, mit gleichzeitigen Änderungen sicher zu entfernen?
Ihre Problemumgehung funktioniert, aber es gibt ein mögliches Szenario. Wenn bestimmte Einträge konstante Aktualisierungen aufweisen, wird map.remove (Schlüssel, Wert) möglicherweise nie den Wert true zurückgeben, bis die Aktualisierungen abgeschlossen sind.
Wenn Sie JDK8 hier verwenden, ist meine Lösung
%Vor%compute () wird f (v) auf den Wert anwenden und in unserem Fall den Wert der globalen Variablen zuweisen und den Eintrag entfernen.
Laut Javadoc ist es atomar.
Versucht, eine Zuordnung für den angegebenen Schlüssel und seinen aktuellen zugeordneten Wert zu berechnen (oder null, wenn keine aktuelle Zuordnung vorhanden ist). Der gesamte Methodenaufruf wird atomar ausgeführt. Einige versuchte Aktualisierungsoperationen in dieser Map durch andere Threads werden während der Berechnung möglicherweise blockiert. Daher sollte die Berechnung kurz und einfach sein und darf nicht versuchen, andere Zuordnungen dieser Map zu aktualisieren.
Ihre Problemumgehung ist eigentlich ziemlich gut. Es gibt andere Möglichkeiten, auf die Sie eine ähnliche Lösung aufbauen können (z. B. mit computeIfPresent()
und Tombstone-Werten), aber sie haben ihre eigenen Vorbehalte und ich habe sie in etwas anderen Anwendungsfällen verwendet.
Wie bei der Verwendung eines Typs, der equals()
für die Map-Werte nicht implementiert, können Sie einen eigenen Wrapper über dem entsprechenden Typ verwenden. Das ist die einfachste Möglichkeit, benutzerdefinierte Semantiken für die Objektgleichheit in die atomaren Ersetzungs- / Entfernungsoperationen einzufügen, die von ConcurrentMap
bereitgestellt werden.
Aktualisieren
Hier ist eine Skizze, die zeigt, wie Sie auf der ConcurrentMap.remove(Object key, Object value)
API aufbauen können:
equals()
-Methode, die über dem aktuellen veränderbaren Wert liegt. BiConsumer
(dem Lambda, das Sie an forEach
übergeben) eine tiefe Kopie des Werts (vom Typ Ihres neuen Wrappertyps) und führen Sie Ihre Logik aus, um zu bestimmen, ob der Wert entfernt werden muss auf der Kopie. remove(myKey, myValueCopy)
auf.
remove(myKey, myValueCopy)
false
zurück (abgesehen von ABA-Problemen, die ein separates Thema sind). Hier ist ein Code, der dies veranschaulicht:
%Vor%Sie sehen die Ausdrucke von "Bailed out!", vermischt mit "Replaced!" (Die Entfernung war erfolgreich, da es keine gleichzeitigen Aktualisierungen gab, die Sie interessieren) und die Berechnung wird irgendwann aufhören.
equals()
-Methode entfernen und weiterhin eine Kopie verwenden, sehen Sie einen endlosen Strom von "Bailed out!", da die Kopie niemals als gleich dem Wert in der Karte betrachtet wird. Lassen Sie uns überlegen, welche Möglichkeiten Sie haben.
Erstellen Sie Ihre eigene Container-Klasse mit der Operation isUpdated()
und verwenden Sie Ihre eigene Problemumgehung.
Wenn Ihre Map nur ein paar Elemente enthält und Sie sehr häufig über die Map iterieren, vergleichen Sie sie mit put / delete. Es könnte eine gute Wahl sein, CopyOnWriteArrayList
zu verwenden
CopyOnWriteArrayList<Entry<Integer, Integer>> lookupArray = ...;
Die andere Möglichkeit besteht darin, Ihre eigene CopyOnWriteMap
Es gibt einen negativen Nebeneffekt. Wenn Sie Copy-on-Write-Sammlungen verwenden, gehen Ihre Aktualisierungen nie verloren, aber Sie können wieder einige frühere gelöschte Einträge sehen.
Im schlimmsten Fall: Der gelöschte Eintrag wird jedes Mal wiederhergestellt, wenn die Karte kopiert wird.
Tags und Links java multithreading concurrenthashmap