Come evitare HashMap “ConcurrentModificationException” durante la manipolazione `values()` e `()` nel thread simultanei?

Codice:

Ho un HashMap

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

Un metodo di mettere K-V coppia chiamando put(K,V).

L’altro metodo vuole estrarre un insieme di elementi casuali dai suoi valori:

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]

I due metodi vengono chiamati in due diverse thread simultanei.


Errore:

Ho avuto un ConcurrentModificationException errore:

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

Sembra che il toArray() metodo in un thread è in realtà scorrendo la HashMap e un put() modifica in altro thread si verifica.

Domanda: Come evitare di “ConcurrentModificationException” durante l’utilizzo di HashMap.i valori di().toArray() e HashMap.put() nel thread simultanei?
Direttamente evitando l’utilizzo di values().toArray() nel secondo metodo è anche OK.

Eseguire il codice che accede al map in un sincronizzare blocco: synchronize(map){...}
synchronize(map){..} dovrebbe funzionare (se si applica ovunque). Collezioni.synchronizedMap non funziona. vedere docs.oracle.com/javase/7/docs/api/java/util/…

OriginaleL’autore hengxin | 2014-10-29

2 Replies
  1. 4

    È necessario fornire un certo livello di sincronizzazione in modo che la chiamata a put è bloccato mentre il toArray chiamata è in esecuzione e viceversa. Ci sono tre due semplici metodi:

    1. Avvolgere le chiamate put e toArray in synchronized blocchi di sincronizzare sul blocco stesso oggetto (che potrebbe essere la stessa mappa o qualche altro oggetto).

    2. Girare la mappa in sincronia mappa utilizzando Collezioni.synchronizedMap()

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

    3. Utilizzare un ConcurrentHashMap invece di un HashMap.

    EDIT: Il problema con l’utilizzo di Collections.synchronizedMap è che una volta che la chiamata a values() restituisce, la concorrenza, la protezione scompare. A quel punto, chiamate a put() e toArray() potrebbe eseguire simultaneamente. Un ConcurrentHashMap ha un problema simile, ma può ancora essere utilizzato. Dal docs per ConcurrentHashMap.valori():

    Vista dell’iteratore è un “poco coerente” iteratore che non sarà mai buttare ConcurrentModificationException, e garanzie per attraversare gli elementi esistenti al momento della costruzione dell’iteratore, e potrebbe (ma non è garantito) riflettere eventuali modifiche successive di costruzione.

    Destra. Tre approcci. 🙂
    Grazie. Tuttavia, ho letto qualche commento che ConcurrentHashMap non necessariamente risolvere ConcurrentModificationException (ma non riuscire a trovare la fonte ora). Perché funziona in questo modo/il mio caso?
    Hai bisogno di values() di lavoro multi-threaded, e il Javadoc dice “La vista dell’iteratore è un “poco coerente” iteratore che non sarà mai buttare ConcurrentModificationException, e garanzie per attraversare gli elementi esistenti al momento della costruzione dell’iteratore, e potrebbe (ma non è garantito) riflettere eventuali modifiche successive di costruzione.”
    “potrebbe (ma non è garantito)”. Se avete bisogno di più rigorose garanzie, aggiungere blocchi sincronizzati a tutti i vostri HashMap accede invece.
    synchronizedMap probabilmente non funzionerà, si vedano le osservazioni circa l’iterazione docs.oracle.com/javase/7/docs/api/java/util/…

    OriginaleL’autore

  2. 0

    Vorrei utilizzare ConcurrentHashMap invece di una HashMap e proteggerlo da concorrenti di lettura e modifica da thread diversi. Vedi di seguito la realizzazione. Non è possibile che il thread 1 e 2 thread per leggere e scrivere allo stesso tempo. Quando il thread 1 è l’estrazione di valori da associare ad un array, tutti gli altri thread che richiamare storeInMap(K, V) di sospensione e di attesa sulla mappa fino a quando il primo thread è fatto con l’oggetto.

    Nota: io non uso il metodo sincronizzato, in questo contesto, io non sono del tutto escludere metodo sincronizzato, ma vorrei usarlo con cautela. Un metodo sincronizzato è in realtà solo la sintassi di zucchero per ottenere un lock su ‘questo’ e lo tiene per tutta la durata del metodo così può far male, la velocità di trasmissione.

    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);
        }
    }
    Perché il syncronized blocchi se si sta già utilizzando una ConcurrentHashMap?

    OriginaleL’autore

Lascia un commento