5

Code:Wie vermeidet man HashMap "ConcurrentModificationException" beim Manipulieren von `values ​​()` und `put()` in parallelen Threads?

Ich habe eine HashMap

private Map<K, V> map = new HashMap<>(); 

Eine Methode wird durch den Aufruf put(K,V) hinein K-V Paar setzen.

Die andere Methode will von ihren Werten einen Satz von Zufallselementen extrahieren:

int size = map.size(); // size > 0 
V[] value_array = map.values().toArray(new V[size]); 
Random rand = new Random(); 
int start = rand.nextInt(size); int end = rand.nextInt(size); 
// return value_array[start .. end - 1] 

Die beiden Methoden sind in zwei verschiedenen gleichzeitige Threads bezeichnet.


Fehler:

Ich bekam einen ConcurrentModificationException Fehler:

at java.util.HashMap$HashIterator.nextEntry(Unknown Source) 
at java.util.HashMap$ValueIterator.next(Unknown Source) 
at java.util.AbstractCollection.toArray(Unknown Source) 

Es scheint, dass die toArray() Methode in einem Thread tatsächlich über die HashMap iteriert und eine put() Modifikation in anderem Thread tritt ein.

Question: How to avoid "ConcurrentModificationException" while using HashMap.values().toArray() and HashMap.put() in concurrent threads?
Directly avoiding using values().toArray() in the second method is also OK.

+1

den Code ausführen, der die 'map' in einem Synchronisierungsblock zugreift:' synchronisieren (Karte) {...} '' – Titus

+1

synchronize (Karte) {..} 'funktionieren sollte (wenn Sie es überall anwenden). Collections.synchronizedMap funktioniert nicht. siehe http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedMap%28java.util.Map%29 – Thilo

Antwort

4

Sie benötigen ein gewisses Maß an Synchronisation bereitzustellen, so dass der Anruf an put blockiert wird, während der toArray Anruf ausgeführt wird und umgekehrt. Es gibt drei zwei einfache Ansätze:

  1. Wickeln Sie Ihre Anrufe zu put und toArray in synchronized Blöcke, die auf dem gleichen Sperrobjekt synchronisieren (die die Karte selbst oder ein anderes Objekt sein könnte).
  2. Ihre Karte in einer synchronisierten Karte drehen Collections.synchronizedMap()

    private Map<K, V> map = Collections.synchronizedMap(new HashMap<>()); 
    

  3. Verwenden Sie ein ConcurrentHashMap anstelle eines HashMap verwenden.

EDIT: Das Problem mit Collections.synchronizedMap ist, dass, sobald der Anruf values() zurückkehrt, wird die Gleichzeitigkeit Schutz verschwinden. An diesem Punkt können Aufrufe an put() und toArray() gleichzeitig ausgeführt werden. A ConcurrentHashMap hat ein etwas ähnliches Problem, aber es kann immer noch verwendet werden. Aus der Dokumentation für ConcurrentHashMap.values():

The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException , and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+0

@Thilo - Richtig. Drei Ansätze. :) –

+0

@ Thilo Danke. Ich habe jedoch einige Bemerkungen gelesen, dass ConcurrentHashMap ConcurrentModificationException nicht unbedingt löst (aber jetzt die Quelle nicht finden kann). Warum funktioniert es in diesem/meinem Fall? – hengxin

+1

Sie benötigen 'values ​​()', um Multi-Threaded zu arbeiten, und der Javadoc sagt: "Der Iterator der Ansicht ist ein" schwach konsistenter "Iterator, der niemals ConcurrentModificationException wirft und garantiert, Elemente zu durchlaufen, wie sie beim Aufbau des Iterators bestanden haben, und kann (ist aber nicht garantiert) Änderungen nach der Konstruktion widerspiegeln. " – Thilo

0

würde ich ConcurrentHashMap anstelle einer HashMap verwenden und von gleichzeitigen Lesen und Modifikation von verschiedenen Threads zu schützen. Siehe die folgende Implementierung. Es ist nicht möglich, dass Thread 1 und Thread 2 gleichzeitig lesen und schreiben. Wenn Thread 1 Werte aus Map in ein Array extrahiert, werden alle anderen Threads, die storeInMap (K, V) aufrufen, angehalten und warten auf der Map, bis der erste Thread mit dem Objekt ausgeführt wird.

Hinweis: Ich verwende in diesem Zusammenhang keine synchronisierte Methode; Ich schließe Synchronisierungsmethode nicht völlig aus, aber ich würde es mit Vorsicht verwenden. Eine synchronisierte Methode ist eigentlich nur die Syntax Zucker, um die Sperre für 'this' zu erhalten und sie für die Dauer der Methode zu halten, damit der Durchsatz beeinträchtigt wird.

private Map<K, V> map = new ConcurrentHashMap<K, V>(); 

// thread 1 
public V[] pickRandom() { 
    int size = map.size(); // size > 0 
    synchronized(map) { 
     V[] value_array = map.values().toArray(new V[size]); 
    } 
    Random rand = new Random(); 
    int start = rand.nextInt(size); 
    int end = rand.nextInt(size); 
    return value_array[start .. end - 1] 
} 

// thread 2 
public void storeInMap(K, V) { 
    synchronized(map) { 
     map.put(K,V); 
    } 
} 
+1

Warum blockieren die 'synchronisierten' Blöcke, wenn Sie bereits eine' ConcurrentHashMap' verwenden? –

Verwandte Themen