web-dev-qa-db-fra.com

Pourquoi une exception ConcurrentModificationException est générée et comment la déboguer

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)
112
mainstringargs

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 ().

243
Robin

Essayez d'utiliser un ConcurrentHashMap au lieu d'un simple HashMap

60
Chochos

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 un ConcurrentModificationException. 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 un IllegalStateException 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).

Débogage 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é.

  • La cause la plus courante est la modification de 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.
  • Un cas plus délicat survient lorsque votre code transmet des références à l'objet 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.
  • Les opérations pouvant modifier un Collection peuvent être inattendues dans certains cas. Par exemple, LinkedHashMap.get() modifie sa collection .
  • Les cas les plus difficiles se produisent lorsque l'exception is en raison d'une modification simultanée par plusieurs threads.

Programmation pour éviter les erreurs de modification simultanées

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.

9
Raedwald

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.

2
duffymo

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();
    }
0
ZhaoGang

Essayez soit CopyOnWriteArrayList ou CopyOnWriteArraySet en fonction de ce que vous essayez de faire.

0
Javamann