Iterate über ConcurrentHashMap beim Löschen von Einträgen

9

Ich möchte beim Entfernen von Einträgen regelmäßig über ein ConcurrentHashMap iterieren:

%Vor%

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?

    
shmosel 10.05.2016, 00:04
quelle

3 Antworten

1

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.

    
xz2145 31.01.2017, 22:43
quelle
1

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:

  • Definieren Sie einen Wrapper-Typ über dem veränderbaren Typ, den Sie für die Werte verwenden, und definieren Sie außerdem Ihre benutzerdefinierte equals() -Methode, die über dem aktuellen veränderbaren Wert liegt.
  • Erstellen Sie in Ihrem 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.
  • Wenn der Wert entfernt werden muss, rufen Sie remove(myKey, myValueCopy) auf.
    • Wenn während der Berechnung, ob der Wert entfernt werden muss, einige gleichzeitige Änderungen aufgetreten sind, gibt 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.

  • Wenn Sie die benutzerdefinierte 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.
  • Wenn Sie keine Kopie verwenden, sehen Sie nicht "Bailed out!" ausgedruckt, und Sie werden das Problem, das Sie erklären, schlagen - Werte werden unabhängig von gleichzeitigen Änderungen entfernt.
Dimitar Dimitrov 10.05.2016 06:52
quelle
0

Lassen Sie uns überlegen, welche Möglichkeiten Sie haben.

  1. Erstellen Sie Ihre eigene Container-Klasse mit der Operation isUpdated() und verwenden Sie Ihre eigene Problemumgehung.

  2. 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 = ...;

  3. Die andere Möglichkeit besteht darin, Ihre eigene CopyOnWriteMap

    zu implementieren %Vor%

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.

    
dit 10.05.2016 07:54
quelle