web-dev-qa-db-fra.com

Exception de modification simultanée

J'ai ce petit morceau de code et cela me donne l'exception de modification simultanée. Je ne comprends pas pourquoi je continue de l'obtenir, même si je ne vois aucune modification simultanée en cours.

import Java.util.*;

public class SomeClass {
    public static void main(String[] args) {
        List<String> s = new ArrayList<>();
        ListIterator<String> it = s.listIterator();

        for (String a : args)
            s.add(a);

        if (it.hasNext())
            String item = it.next();

        System.out.println(s);
    }
}
43
Ankit

Pour éviter le ConcurrentModificationException, vous devez écrire votre code comme ceci:

import Java.util.*;

public class SomeClass {

    public static void main(String[] args) {
        List<String> s = new ArrayList<String>();

        for(String a : args)
            s.add(a);

        ListIterator<String> it = s.listIterator();    
        if(it.hasNext()) {  
            String item = it.next();   
        }  

        System.out.println(s);

    }
}

UNE Java.util.ListIterator vous permet de modifier une liste pendant l'itération, mais pas entre sa création et son utilisation.

51
Pascal Thivent

Je ne comprends pas pourquoi je continue de l'obtenir, même si je ne vois aucune modification simultanée en cours.

Entre la création de l'itérateur et le début d'utilisation de l'itérateur, vous avez ajouté des arguments à la liste à itérer. Il s'agit d'une modification simultanée.

    ListIterator<String> it = s.listIterator();  

    for (String a : args)
        s.add(a);                    // concurrent modification here

    if (it.hasNext())
        String item = it.next();     // exception thrown here

Créez l'itérateur APRÈS avoir terminé d'ajouter des éléments à la liste:

    for (String a : args)
        s.add(a); 

    ListIterator<String> it = s.listIterator();  
    if (it.hasNext())
        String item = it.next();
44
Stephen C

D'après JavaDoc: pour ConcurrentModificatoinException: "il n'est généralement pas autorisé pour un thread de modifier une collection pendant qu'un autre thread l'itère".

Cela signifie simplement que si vous avez encore un itérateur ouvert, vous n'êtes pas autorisé à modifier la liste car la boucle de l'itérateur se rompra. Essayez de déplacer ListIterator<String> it = s.listIterator(); jusqu'après la boucle for.

11
leonm

Vous n'êtes pas autorisé à continuer l'itération sur un itérateur après la modification de la liste sous-jacente. Ici, vous créez l'itérateur avant d'ajouter quelques éléments à s, puis procédez à une hasNext() et une next() dessus après les ajouts, conduisant à la ConcurrentModificationException

8
Yuliy

Si les solutions ci-dessus ne fonctionnent pas correctement. Vous pouvez utiliser l'ancienne boucle for pour itérer une liste en ajoutant de nouveaux éléments. Voir l'exemple ci-dessous:

import Java.util.*;

public class SomeClass {
    public static void main(String[] args) {
        ArrayList<AClass> aList = new ArrayList<AClass>(); // we will iterate this


        // this will cause ConcurrentModificationException. 
        // Since we are iterating the list, at the same time modifying it.
        /*for(AClass a: aList){
           aList.add(someMethod(a));
        }*/

        // old fashion for-loop will help
        int limit = aList.size();
        for(int i=0; ctr<limit; ++i){
           AClass a = aList.get(i);
           aList.add(someMethod(a));
        }


    }
}
5
jzarsuelo

ConcurrentModificationException peut survenir à la fois dans un environnement à thread unique et dans un environnement à plusieurs threads . Le principal problème est que tous les itérateurs à usage général (comme celui utilisé dans ArrayList) sont tous les itérateurs FailFast , ce qui échoue lorsque nous essayons de modifier une liste si un itérateur itère déjà dessus. Solution -> Utilisez CopyOnWriteArrayList si un tel scénario est requis par l'exigence plutôt que d'utiliser ArrayList.

Pour une démonstration complète de cela, le code mentionné ci-dessous peut être utilisé. Nous avons juste besoin de changer l'implémentation de CopyOnWriteArrayList en ArrayList.

import Java.util.ArrayList;
import Java.util.Iterator;
import Java.util.List;
import Java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author narif
 *
 */
public class TestApp {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> testList = new ArrayList<>();
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add(6, "abcAtindex6");
        int size = testList.size();
        System.out.println("The Current List (ArrayList) is: " + testList);
        System.out.println("The size of the List (ArrayList) is: " + size);
        /* Comment the below lines to get the ConcurrentModificationException */
        testList = new CopyOnWriteArrayList<>(testList);
        for (String value : testList) {
            System.out.println("The Value from ForEach Loop is: " + value);
            /*
             * Concurrent modification is happening here
             * One iterator is iterating over the list while we are trying to add new values to
             * the list so the results of the iteration are undefined under these circumstances.
             * So teh fail fast iterators will fail and will throw the ConcurrentModificationException.
             */
            testList.add("valueFromForLoop");
            testList.add("anotherValueFromForEachLoop");
        }
        Iterator<String> it = testList.iterator();
        while (it.hasNext()) {
            String abc = it.next();
            System.out.println(abc);
            testList.add("Value from Iterator1");
            testList.add("Value from Iterator2");
            testList.add("Value from Iterator3");
            testList.add("Value from Iterator4");

        }
        System.out.println("Did the modificationa and all after conevrting the ArrayList to CopyOnWriteArrayList.");
        System.out.println("Calling the method to get the new List..");
        testList = new CopyOnWriteArrayList<>(getTheList(testList));
        for (String value : testList) {
            System.out.println("The value returned from method is : " + value);
        }
    }

    private static List<String> getTheList(List<String> pList) {
        List<String> list = new CopyOnWriteArrayList<>(pList);
        int i = 0;
        for (String lValue : list) {
            System.out.println("The list Passed is " + list);
            i++;
            list.add("localVaueFromMethod" + i);
            list.removeAll(pList);
        }
        return list;
    }

}

Pour plus d'informations, suivez ce lien, cela peut être très utile ConcurrentModificationException Java Docs

3
Najeeb Arif

pour comprendre cela, regardons la source de l'implémentation de HashMap:

public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable{

qui contient HashIterator comme ci-dessous:

private abstract class HashIterator {
    ...
    int expectedModCount = modCount;
    ...

    HashMapEntry<K, V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        .... 
        }

chaque fois que vous créez un itérateur:

  • un compteur expectModCount est créé et est défini sur la valeur modCount en entrée point de contrôle
  • modCount est incrémenté en cas d'utilisation put/get (ajout/suppression)
  • La méthode nextEntry de l'itérateur vérifie cette valeur avec le modCount actuel s'il s'agit d'une exception de modification simultanée différente est throw

pour éviter cela, vous pouvez:

  • convertir la carte en un tableau (non recommandé pour les grandes cartes)
  • utiliser une carte de concurrence ou des classes de liste ( CopyOnWriteArrayList / ConcurrentMap )
  • verrouiller la carte (cette approche supprime les avantages du multithreading)

cela vous permettra d'itérer et d'ajouter ou de supprimer des éléments en même temps sans lever une exception

L'itérateur de carte/liste de concurrence est un itérateur "faiblement cohérent" qui ne lèvera jamais ConcurrentModificationException, et garantit de parcourir les éléments tels qu'ils existaient lors de la construction de l'itérateur, et peut (mais n'est pas garanti) refléter toutes les modifications ultérieures à la construction.

Plus d'informations sur CopyOnWriteArrayList

1
ceph3us

Cela n'a pas fonctionné:

LinkedList<String> linkedList = new LinkedList<String>();
ListIterator listIterator = linkedList.listIterator();
linkedList.add("aa");
linkedList.add("bb");

Cela a fonctionné:

LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("aa");
linkedList.add("bb");
ListIterator listIterator = linkedList.listIterator();
1
Nurgul Alshyn

Jetez un œil à la page Oracle documentation .

public class ConcurrentModificationException
extends RuntimeException

Cette exception peut être levée par des méthodes qui ont détecté une modification simultanée 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. Par exemple, si un thread modifie directement une collection pendant qu'il itère sur la collection avec un itérateur à échec rapide, l'itérateur lèvera cette exception .

Dans votre cas, vous avez modifié la collection après avoir créé l'itérateur et vous avez donc rencontré l'exception.

Si vous modifiez votre code selon la réponse Stephen C, vous n'obtiendrez pas cette erreur.

1
Ravindra babu