Ist das Iterieren von ConcurrentHashMap-Werten threadsicher?

In javadoc für ConcurrentHashMap ist Folgendes:

Wiederherstellungsoperationen (einschließlich get) werden im Allgemeinen nicht blockiert und können sich daher mit Aktualisierungsoperationen (einschließlich Put und Remove) überschneiden. Retrievals spiegeln die Ergebnisse der zuletzt abgeschlossenen Aktualisierungsoperationen wider, die an ihrem Beginn anhalten. Für Aggregatoperationen wie putAll und clear können gleichzeitige Abrufvorgänge das Einfügen oder Entfernen von nur einigen Einträgen widerspiegeln. In ähnlicher Weise geben Iteratoren und Enumerationen Elemente zurück, die den Zustand der Hash-Tabelle zu irgendeinem Zeitpunkt bei oder seit der Erzeugung des Iterators / der Enumeration widerspiegeln. Sie casting ConcurrentModificationException nicht aus. Iteratoren können jedoch nur von jeweils einem Thread verwendet werden.

Was heißt das? Was passiert, wenn ich versuche, die Karte mit zwei Threads gleichzeitig zu durchlaufen? Was passiert, wenn ich einen Wert während der Iteration aus der Map entferne oder entferne?

Solutions Collecting From Web of "Ist das Iterieren von ConcurrentHashMap-Werten threadsicher?"

Was heißt das?

Das bedeutet, dass jeder Iterator, den Sie von einer ConcurrentHashMap , für die Verwendung durch einen einzelnen Thread ausgelegt ist und nicht weitergegeben werden sollte. Dies beinhaltet den syntaktischen Zucker, den die for-each-Schleife bereitstellt.

Was passiert, wenn ich versuche, die Karte mit zwei Threads gleichzeitig zu durchlaufen?

Es wird wie erwartet funktionieren, wenn jeder der Threads seinen eigenen Iterator verwendet.

Was passiert, wenn ich einen Wert während der Iteration aus der Map entferne oder entferne?

Es ist garantiert, dass die Dinge nicht kaputt gehen, wenn Sie dies tun (das ist Teil dessen, was das “gleichzeitige” in ConcurrentHashMap bedeutet). Es gibt jedoch keine Garantie, dass ein Thread die Änderungen an der Map sieht, die der andere Thread ausführt (ohne einen neuen Iterator von der Map zu erhalten). Der Iterator spiegelt den Zustand der Karte zum Zeitpunkt ihrer Erstellung garantiert wider. Weitere Änderungen können sich im Iterator widerspiegeln, müssen dies aber nicht sein.

Abschließend eine Aussage wie

 for (Object o : someConcurrentHashMap.entrySet()) { // ... } 

wird fast immer gut (oder zumindest sicher) sein, wenn Sie es sehen.

Sie können diese class verwenden, um zwei zugreifende Threads zu testen, und eine, die die gemeinsam genutzte Instanz von ConcurrentHashMap mutiert:

 import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Map map; public Accessor(Map map) { this.map = map; } @Override public void run() { for (Map.Entry entry : this.map.entrySet()) { System.out.println( Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']' ); } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); System.out.println(Thread.currentThread().getName() + ": " + i); } } } private void run() { Accessor a1 = new Accessor(this.map); Accessor a2 = new Accessor(this.map); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Es wird keine Ausnahme ausgetriggers.

Das Teilen desselben Iterators zwischen Accessor-Threads kann zu einem Deadlock führen:

 import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while(iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Sobald Sie beginnen, den gleichen Iterator

>

unter den Accessor- und Mutator-Threads java.lang.IllegalStateException s gestartet.

 import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Random random = new Random(); private final Iterator> iterator; private final Map map; public Mutator(Map map, Iterator> iterator) { this.map = map; this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { try { iterator.remove(); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } catch (Exception ex) { ex.printStackTrace(); } } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(map, this.iterator); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Dies bedeutet, dass Sie kein Iterator-Objekt unter mehreren Threads teilen sollten. Es ist in Ordnung, mehrere Iteratoren zu erstellen und sie gleichzeitig in separaten Threads zu verwenden.

Dies könnte Ihnen einen guten Einblick geben

ConcurrentHashMap erreicht höhere Nebenläufigkeit, indem es die Versprechen, die es an Anrufer macht, etwas lockert. Eine Abrufoperation gibt den Wert zurück, der von der zuletzt abgeschlossenen Einfügeoperation eingefügt wurde, und kann auch einen Wert zurückgeben, der von einer gleichzeitig ausgeführten Einfügeoperation hinzugefügt wurde (aber in keinem Fall wird ein Unsinnsergebnis zurückgegeben). Iteratoren, die von ConcurrentHashMap.iterator () zurückgegeben werden, geben jedes Element höchstens einmal zurück und casting ConcurrentModificationException nicht immer ab, sie können jedoch Einfügungen oder Entfernungen widerspiegeln, die seit der Erstellung des Iterators aufgetreten sind . Eine tabellenweite Sperre ist nicht erforderlich (oder sogar möglich), um Thread-Sicherheit beim Iterieren der Sammlung bereitzustellen. ConcurrentHashMap kann als Ersatz für synchronizedMap oder Hashtable in jeder Anwendung verwendet werden, die nicht auf die Fähigkeit angewiesen ist, die gesamte Tabelle zu sperren, um Aktualisierungen zu verhindern.

Bezüglich dieser:

Iteratoren können jedoch nur von jeweils einem Thread verwendet werden.

Das bedeutet, dass die Verwendung von Iteratoren, die von ConcurrentHashMap in zwei Threads erstellt wurden, sicher ist. Dies kann jedoch zu einem unerwarteten Ergebnis in der Anwendung führen.

Was heißt das?

Das bedeutet, dass Sie nicht versuchen sollten, denselben Iterator in zwei Threads zu verwenden. Wenn Sie zwei Threads haben, die über die Schlüssel, Werte oder Einträge iterieren müssen, sollten sie jeweils ihre eigenen Iteratoren erstellen und verwenden.

Was passiert, wenn ich versuche, die Karte mit zwei Threads gleichzeitig zu durchlaufen?

Es ist nicht ganz klar, was passieren würde, wenn Sie diese Regel brechen würden. Sie könnten ein verwirrendes Verhalten bekommen, genauso wie Sie es tun, wenn (zum Beispiel) zwei Threads versuchen, von der Standardeingabe zu lesen, ohne zu synchronisieren. Sie können auch nicht Thread-sicheres Verhalten erhalten.

Aber wenn die beiden Threads unterschiedliche Iteratoren verwenden, sollte alles in Ordnung sein.

Was passiert, wenn ich einen Wert während der Iteration aus der Map entferne oder entferne?

Das ist ein separates Problem, aber der von Ihnen zitierte Javadoc-Abschnitt beantwortet dies angemessen. Grundsätzlich sind die Iteratoren Thread-sicher, aber es ist nicht definiert, ob Sie die Auswirkungen von gleichzeitigen Einfügungen, Aktualisierungen oder Löschungen in der Folge der vom Iterator zurückgegebenen Objekte widerspiegeln werden. In der Praxis hängt es wahrscheinlich davon ab, wo in der Karte die Aktualisierungen stattfinden.