La modification d'une Collectionitération while à l' Collectionaide de an Iteratorn'est pas autorisée par la plupart des Collectionclasses. La bibliothèque Java appelle une tentative de modification d'un Collectiontout en l'itérant une "modification simultanée". Cela suggère malheureusement que la seule cause possible est la modification simultanée par plusieurs threads, mais ce n'est pas le cas. En utilisant un seul thread, il est possible de créer un itérateur pour Collection(en utilisant Collection.iterator(), ou une boucle amélioréefor ), de démarrer l'itération (en utilisant Iterator.next()ou en entrant de manière équivalente le corps de la forboucle améliorée ), de modifier le Collection, puis de continuer l'itération.
Pour aider les programmeurs, certaines implémentations de ces Collectionclasses tentent de détecter une modification concurrente erronée et lancent un ConcurrentModificationExceptionsi elles la détectent. Cependant, il n'est en général pas possible et pratique de garantir la détection de toutes les modifications concurrentes. Ainsi, une utilisation erronée de l ' Collectionn'entraîne pas toujours un jet ConcurrentModificationException.
La documentation de ConcurrentModificationExceptiondit:
Cette exception peut être levée par des méthodes qui ont détecté une modification concurrente d'un objet lorsqu'une telle modification n'est pas autorisée ...
Notez que cette exception n'indique pas toujours qu'un objet a été modifié simultanément par un thread différent. Si un seul thread émet une séquence d'appels de méthode qui viole le contrat d'un objet, l'objet peut lever cette exception ...
Notez que le comportement à échec rapide ne peut pas être garanti car il est, d'une manière générale, impossible de faire des garanties fermes en présence de modifications concurrentes non synchronisées. Les opérations échouées sont basées ConcurrentModificationExceptionsur le meilleur effort.
Notez que
La documentation du HashSet, HashMap, TreeSetet les ArrayListclasses dit ceci:
Les itérateurs retournés [directement ou indirectement de cette classe] sont rapides: 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 via la propre méthode remove de l'itérateur, la commande Iteratorrenvoie un ConcurrentModificationException. Ainsi, face à une modification concurrente, l'itérateur échoue rapidement et proprement, plutôt que de risquer un comportement arbitraire et non déterministe à un moment indéterminé dans le futur.
Notez que le comportement de défaillance rapide d'un itérateur ne peut pas être garanti car il est, d'une manière générale, impossible de faire des garanties matérielles en présence de modifications concurrentes non synchronisées. Les itérateurs rapides échouent ConcurrentModificationExceptionsur la base du meilleur effort. Par conséquent, il serait erroné d'écrire un programme qui dépendait de cette exception pour son exactitude: le comportement rapide des itérateurs ne devrait être utilisé que pour détecter les bogues .
Notez à nouveau que le comportement «ne peut être garanti» et n'est que «au mieux».
La documentation de plusieurs méthodes de l' Mapinterface dit ceci:
Les implémentations non simultanées devraient remplacer cette méthode et, au mieux, lancer un ConcurrentModificationExceptions'il est détecté que la fonction de mappage modifie cette carte pendant le calcul. Les implémentations concurrentes doivent remplacer cette méthode et, au mieux, lancer un IllegalStateExceptions'il est détecté que la fonction de mappage modifie cette carte pendant le calcul et que, par conséquent, le calcul ne se terminerait jamais.
Notez à nouveau que seule une "base de meilleur effort" est requise pour la détection, et a ConcurrentModificationExceptionn'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 à a ConcurrentModificationException, vous ne pouvez pas immédiatement supposer que la cause est un accès multithread non sécurisé à un fichier Collection. Vous devez examiner la trace de pile pour déterminer quelle classe de a Collectionlancé l'exception (une méthode de la classe l'aura directement ou indirectement lancée), et pour quel Collectionobjet. Ensuite, vous devez examiner d'où cet objet peut être modifié.
- La cause la plus fréquente est la modification de l'
Collectionintérieur d'une forboucle améliorée sur le Collection. Ce n'est pas parce que vous ne voyez pas d' Iteratorobjet dans votre code source qu'il n'y en a pas Iterator! Heureusement, l'une des instructions de la forboucle défectueuse se trouve généralement dans la trace de la pile, il est donc généralement facile de localiser l'erreur.
- Un cas plus délicat est celui où votre code passe autour des références à l'
Collectionobjet. Notez que les vues non modifiables des collections (telles que produites par Collections.unmodifiableList()) conservent une référence à la collection modifiable, donc l' itération sur une collection "non modifiable" peut lever l'exception (la modification a été faite ailleurs). D'autres vues de votre Collection, telles que les sous-listes , Maples jeux d'entrées et Maples jeux de clés conservent également des références à l'original (modifiable) Collection. Cela peut être un problème même pour un thread-safe Collection, tel que CopyOnWriteList; ne supposez pas que les collections thread-safe (simultanées) ne peuvent jamais lever l'exception.
- Les opérations qui peuvent modifier un
Collectionpeuvent être inattendues dans certains cas. Par exemple, LinkedHashMap.get()modifie sa collection .
- Les cas les plus difficiles sont ceux où l'exception est due à 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 Collectionobjet, il est donc plus facile d'empêcher les modifications simultanées. Créez Collectionun privateobjet ou une variable locale et ne renvoyez pas de références à Collectionou à ses itérateurs à partir des méthodes. Il est alors beaucoup plus facile d'examiner tous les endroits où le Collectionpeut être modifié. Si le Collectiondoit être utilisé par plusieurs threads, il est alors pratique de s'assurer que les threads accèdent au Collectionseul avec une synchronisation et un verrouillage appropriés.