2009-03-02 11 views
94

Sto usando oggetti persistenti usando JPA. L'oggetto Main ha una relazione One-Many proprietaria con un altro oggetto. L'altro oggetto è memorizzato in una HashMap. Quale tipo di sincronizzazione risolverebbe questo problema? Sembra che avvenga in momenti completamente casuali ed è molto imprevedibile. Ecco l'eccezione che ottengo:ConcurrentModificationException e una HashMap

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException 
     at java.util.HashMap$HashIterator.nextEntry(Unknown Source) 
     at java.util.HashMap$ValueIterator.next(Unknown Source) 
     at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555) 
     at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296) 
     at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242) 
     at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219) 
     at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169) 
     at org.hibernate.engine.Cascade.cascade(Cascade.java:130) 
+1

Potete fornire qualche altro co ntext? Stai unendo, aggiornando o eliminando un'entità? Quali associazioni esercitano questa entità? E le impostazioni a cascata? – ordnungswidrig

+0

Dalla traccia dello stack è possibile vedere che l'eccezione si verifica durante l'iterazione tramite HashMap. Sicuramente qualche altro thread sta modificando la mappa ma l'eccezione si verifica nel thread che sta iterando. – Chochos

+0

Possibile duplicato di [Iterazione attraverso una raccolta, evitando ConcurrentModificationException durante la rimozione in loop] (http://stackoverflow.com/questions/223918/iterating-through-a-collection-avoiding-concurrentmodificationexception-when-re) – Raedwald

risposta

205

Questo non è un problema di sincronizzazione. Ciò si verificherà se la raccolta sottostante che viene iterata sopra viene modificata da qualcosa di diverso da Iterator stesso.

Iterator it = map.entrySet().iterator(); 
while (it.hasNext()) 
{ 
    Entry item = it.next(); 
    map.remove(item.getKey()); 
} 

Questo lancerà una ConcurrentModificationException quando l'it.hasNext() viene chiamato per la seconda volta.

L'approccio corretto sarebbe

Iterator it = map.entrySet().iterator(); 
    while (it.hasNext()) 
    { 
     Entry item = it.next(); 
     it.remove(); 
    } 

Assumendo questo iteratore supporta l'operazione di rimozione().

+1

Probabilmente, ma sembra che Hibernate stia eseguendo l'iterazione, che dovrebbe essere implementato in modo ragionevolmente corretto. Potrebbe esserci una richiamata che modifica la mappa, ma è improbabile. L'imprevedibilità indica un problema di concorrenza reale. –

+0

Questa eccezione non ha nulla a che fare con la concomitanza del threading, è causata dal backing store dell'iteratore che si sta modificando. Se con un altro thread non è importante per l'iteratore. IMHO è un'eccezione con un nome errato poiché dà un'impressione errata della causa. – Robin

+0

Concordo tuttavia sul fatto che se è imprevedibile, è probabile che si verifichi un problema di threading che causa le condizioni per questa eccezione. Il che rende ancora più complicato il nome dell'eccezione. – Robin

3

Sembra meno un problema di sincronizzazione Java e più simile a un problema di blocco del database.

Non so se aggiungere una versione a tutte le classi persistenti lo risolve, ma è un modo in cui Hibernate può fornire accesso esclusivo alle righe in una tabella.

Potrebbe essere che il livello di isolamento deve essere superiore. Se permetti "letture sporche", forse hai bisogno di andare su serializzabile.

+0

HashMap è thread -sicuro. Non è un problema di solarizzazione. – TBH

+1

Mi hai votato? Non hai letto la mia risposta? Ho detto che si trattava di un problema di blocco del database, non di "sincronizzazione". Le tue capacità di lettura sono scarse quanto la tua ortografia. – duffymo

+1

@TBH In che modo HashMap è sicuro per i thread? –

1

Provare CopyOnWriteArrayList o CopyOnWriteArraySet a seconda di cosa si sta tentando di fare.

51

Provare a usare una ConcurrentHashMap al posto di un HashMap pianura

+0

Questo ha veramente risolto il problema? Sto riscontrando lo stesso problema ma posso sicuramente escludere eventuali problemi di threading. – tobiasbayer

+2

Un'altra soluzione è creare una copia della mappa e iterare attraverso quella copia. Oppure copia il set di chiavi e itera attraverso di esse, ottenendo il valore per ogni chiave dalla mappa originale. – Chochos

+0

È Hibernate che sta iterando attraverso la raccolta, quindi non puoi semplicemente copiarlo. – tobiasbayer

-1

Forse un'altra soluzione potrebbe essere quella di acquisire un blocco prima di iniziare la modifica/persistenza in modo da non avere qualche altro thread modificare ciò che stai iterazione

private ReadWriteLock lock = new ReentrantReadWriteLock(); 
lock.writeLock().lock(); 
try{ 
//itterate and persist 
} 
finally{ 
lock.writeLock().unlock(); 
    } 
  • Se non si sta facendo alcuna manipolazione forse lock.readLock(). blocco() è anche OK