J'utilise un Collection
(un HashMap
utilisé indirectement par le JPA, il se trouve), mais apparemment, le code jette un ConcurrentModificationException
. Quelle est la cause et comment puis-je résoudre ce problème? En utilisant une synchronisation, peut-être?
Voici la trace de pile complète:
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)
Ce n'est pas un problème de synchronisation. Cela se produit si la collection sous-jacente qui est itérée est modifiée par autre chose que l'Iterator lui-même.
Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
Entry item = it.next();
map.remove(item.getKey());
}
Cela lève une exception ConcurrentModificationException lorsque le it.hasNext () est appelé une deuxième fois.
La bonne approche serait
Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
Entry item = it.next();
it.remove();
}
En supposant que cet itérateur supporte l'opération remove ().
Essayez d'utiliser un ConcurrentHashMap au lieu d'un simple HashMap
Modification d'un Collection
en itérant à travers ce Collection
à l'aide d'un Iterator
est non autorisé par la plupart des Collection
classes. La bibliothèque Java appelle une tentative de modification d'un Collection
tout en itérant une "modification simultanée", ce qui suggère malheureusement que la seule cause possible est la modification simultanée par plusieurs threads, mais En utilisant un seul thread, il est possible de créer un itérateur pour le Collection
(en utilisant Collection.iterator()
, ou un amélioré for
loop ), commencez à itérer (à l'aide de Iterator.next()
, ou entrez de manière équivalente dans le corps de la boucle améliorée for
), modifiez le Collection
, puis continuez l'itération.
Pour aider les programmeurs, certains implémentations de ces Collection
classes tentatives détecter les modifications simultanées erronées et lancer un ConcurrentModificationException
si ils le détectent. Cependant, il n’est généralement ni possible ni pratique de garantir la détection de toutes les modifications simultanées. Ainsi, une utilisation erronée de Collection
ne provoque pas toujours un jeté ConcurrentModificationException
.
La documentation de ConcurrentModificationException
dit:
Cette exception peut être levée par des méthodes qui ont détecté une modification simultanée d'un objet lorsque cette modification n'est pas autorisée ...
Notez que cette exception n'indique pas toujours qu'un objet a été modifié simultanément par un autre thread. Si un seul thread émet une séquence d'appels de méthodes qui ne respecte pas le contrat d'un objet, celui-ci peut lancer cette exception ...
Notez que le comportement à la défaillance rapide ne peut pas être garanti car il est en général impossible de faire des garanties formelles en présence de modifications simultanées non synchronisées. Les opérations qui échouent rapidement jettent
ConcurrentModificationException
au mieux.
Notez que
La documentation de HashSet
, HashMap
, TreeSet
et ArrayList
classes dit ceci:
Les itérateurs retournés [directement ou indirectement à partir de cette classe] ont la capacité d'échec rapide: si la [collection] est modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par le biais de sa propre méthode de suppression, le
Iterator
jette unConcurrentModificationException
. Ainsi, face aux modifications simultanées, l'itérateur échoue rapidement et proprement, au lieu de risquer un comportement non déterministe arbitraire à une date future indéterminée.Notez que le comportement de défaillance d'un itérateur ne peut pas être garanti car il est généralement impossible de donner des garanties absolues en présence de modifications simultanées non synchronisées. Les itérateurs qui échouent rapidement lancent
ConcurrentModificationException
au mieux. Par conséquent, il serait erroné d'écrire un programme qui dépend de cette exception pour son exactitude: le comportement d'échec rapide des itérateurs ne devrait être utilisé que pour détecter des bogues.
Notez à nouveau que le comportement "ne peut pas être garanti" et est uniquement "au mieux des efforts".
La documentation de plusieurs méthodes de l'interface Map
dit ceci:
Les implémentations non simultanées doivent remplacer cette méthode et, au mieux, lancer un
ConcurrentModificationException
s'il est détecté que la fonction de mappage modifie cette mappe pendant le calcul. Les implémentations simultanées doivent remplacer cette méthode et, au mieux, lancer unIllegalStateException
s'il est détecté que la fonction de mappage modifie cette mappe pendant le calcul et que, par conséquent, le calcul ne serait jamais terminé.
Notez à nouveau que seule la "base du meilleur effort" est requise pour la détection et qu'un ConcurrentModificationException
n'est explicitement suggéré que pour les classes non concurrentes (non thread-safe).
ConcurrentModificationException
Ainsi, lorsque vous voyez une trace de pile due à un ConcurrentModificationException
, vous ne pouvez pas immédiatement en déduire que la cause en est un accès multithread non sécurisé à un Collection
. Vous devez examiner la trace de pile pour déterminer quelle classe de Collection
a levé l'exception (une méthode de la classe l'aura directement ou indirectement lancée) et pour laquelle Collection
objet. Ensuite, vous devez examiner d'où cet objet peut être modifié.
Collection
dans une boucle améliorée for
par-dessus le Collection
. Ce n'est pas parce que vous ne voyez pas d'objet Iterator
dans votre code source qu'il n'y a pas de Iterator
! Heureusement, une des déclarations de la boucle défectueuse for
sera généralement dans la trace de pile, il est donc facile de localiser l'erreur.Collection
. Notez que non modifiable les vues de collections (telles que celles produites par Collections.unmodifiableList()
) conservent une référence à la collection modifiable, donc itération sur une collection "non modifiable" peut renvoyer l'exception (la modification a été effectuée ailleurs). Autre vues de votre Collection
, tel que sous-listes , Map
ensembles d'entrées et - Map
jeux de clés conserve également les références à l'original (modifiable) Collection
. Cela peut être un problème même pour un thread-safe Collection
, tel que CopyOnWriteList
; ne présumez pas que les collectines sécurisées pour les threads (simultanées) ne peuvent jamais lever l'exception.Collection
peuvent être inattendues dans certains cas. Par exemple, LinkedHashMap.get()
modifie sa collection .Lorsque cela est possible, limitez toutes les références à un objet Collection
afin d’éviter les modifications simultanées. Faites de Collection
un objet private
ou une variable locale et ne renvoyez pas de références à Collection
ni à ses itérateurs de méthodes. Il est alors beaucoup plus facile d’examiner tous les endroits où le Collection
peut être modifié. Si le Collection
doit être utilisé par plusieurs threads, il est alors pratique de s’assurer que les threads n’accèdent au Collection
qu’avec la synchronisation et le verrouillage appropriés.
Cela ressemble moins à un problème de synchronisation Java) qu’à un problème de verrouillage de la base de données.
Je ne sais pas si l'ajout d'une version à toutes vos classes persistantes réglera le problème, mais c'est une des façons qu'Hibernate peut fournir un accès exclusif aux lignes d'une table.
Peut-être que le niveau d'isolement doit être plus élevé. Si vous autorisez les "lectures incorrectes", vous devrez peut-être passer en mode sérialisable.
Notez que la réponse sélectionnée ne peut pas être appliquée à votre contexte directement avant une modification, si vous essayez de supprimer certaines entrées de la carte tout en itérant la carte comme moi.
Je donne juste mon exemple de travail ici pour les débutants pour économiser leur temps:
HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
//it.remove() will delete the item from the map
if((Integer)item.getValue()<threshold){
it.remove();
}
Essayez soit CopyOnWriteArrayList ou CopyOnWriteArraySet en fonction de ce que vous essayez de faire.