web-dev-qa-db-fra.com

Exception de modification simultanée: ajout à une liste de tableaux

Le problème se pose à 

Element element = it.next();

Et ce code qui contient cette ligne est à l'intérieur d'une OnTouchEvent

for (Iterator<Element> it = mElements.iterator(); it.hasNext();){
    Element element = it.next();

    if(touchX > element.mX  && touchX < element.mX + element.mBitmap.getWidth() && touchY > element.mY   
            && touchY < element.mY + element.mBitmap.getHeight()) {  

        //irrelevant stuff..

        if(element.cFlag){
            mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            element.cFlag = false;

        }           
    }
}

Tout cela se trouve dans synchronized(mElements), où mElements est un ArrayList<Element> 

Lorsque je touche une Element, il peut activer cFlag, ce qui créera une autre Element avec différentes propriétés, qui tombera de l'écran et se détruira en moins d'une seconde. C'est ma façon de créer des effets de particules. Nous pouvons appeler cette "particule" crack, comme le paramètre String du constructeur.

Tout cela fonctionne bien jusqu'à ce que j'ajoute une autre variable principale Element. Maintenant, j'ai deux Elements à l'écran en même temps, et si je touche la dernière Element, cela fonctionne bien et lance les particules.

Cependant, si je touche et active cFlag sur la plus ancienne Element, cela me donne l'exception.

 07-28 15:36:59.815: ERROR/AndroidRuntime(4026): FATAL EXCEPTION: main
07-28 15:36:59.815: ERROR/AndroidRuntime(4026): Java.util.ConcurrentModificationException
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Java.util.ArrayList$ArrayListIterator.next(ArrayList.Java:573)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Juggle2.Panel.onTouchEvent(Panel.Java:823)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.view.View.dispatchTouchEvent(View.Java:3766)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:863)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:863)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.Java:1767)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.Java:1119)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.app.Activity.dispatchTouchEvent(Activity.Java:2086)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.Java:1751)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.view.ViewRoot.handleMessage(ViewRoot.Java:1785)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.os.Handler.dispatchMessage(Handler.Java:99)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.os.Looper.loop(Looper.Java:123)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Android.app.ActivityThread.main(ActivityThread.Java:4627)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Java.lang.reflect.Method.invokeNative(Native Method)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at Java.lang.reflect.Method.invoke(Method.Java:521)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:893)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:651)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at dalvik.system.NativeStart.main(Native Method)

Comment puis-je faire ce travail?

39
Houseman

ConcurrentModificationException se produit lorsque vous modifiez la liste (en ajoutant ou en supprimant des éléments) tout en parcourant une liste avec Iterator.

Essayer 

List<Element> thingsToBeAdd = new ArrayList<Element>();
for(Iterator<Element> it = mElements.iterator(); it.hasNext();) {
    Element element = it.next();
    if(...) {  
        //irrelevant stuff..
        if(element.cFlag){
            // mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            thingsToBeAdd.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            element.cFlag = false;
        }           
    }
}
mElements.addAll(thingsToBeAdd );

Aussi, vous devriez envisager une amélioration pour chaque boucle, comme suggéré par Jon.

60
user802421

J'utilise normalement quelque chose comme ça:

for (Element element : new ArrayList<Element>(mElements)) {
    ...
}

rapide, propre et sans bug

une autre option consiste à utiliser CopyOnWriteArrayList

37
konmik

Vous n'êtes pas autorisé à ajouter une entrée à une collection lorsque vous le parcourez.

Une option consiste à créer un nouveau List<Element> pour les nouvelles entrées pendant une itération sur mElements, puis à ajouter toutes les nouvelles entrées à mElement par la suite (mElements.addAll(newElements)). Bien sûr, cela signifie que vous n'avez pas exécuté le corps de la boucle pour ces nouveaux éléments - est-ce un problème?

Dans le même temps, je vous recommande de mettre à jour votre code pour utiliser le amélioré pour la boucle :

for (Element element : mElements) {
    ...
}
20
Jon Skeet

Une boucle indexée pour devrait également fonctionner.

for (int i = 0; i < collection.size(); i++)
10
Ixx

ajouter de la liste dans ce cas conduit à CME, aucune quantité de synchronized ne vous permettra d'éviter cela. Au lieu de cela, pensez à ajouter en utilisant l'itérateur ...

        for(ListIterator<Element> it = mElements.listIterator(); it.hasNext();){
            Element element = it.next();

            if(touchX > element.mX  && touchX < element.mX + element.mBitmap.getWidth() && touchY > element.mY   
                    && touchY < element.mY + element.mBitmap.getHeight()) {  

                //irrelevant stuff..

                if(element.cFlag){
                    // mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
                    it.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
                    element.cFlag = false;

                }           
            }
        }

Aussi, je pense que c'est un peu glissant de dire comme ...

... Le problème se produit à Element element = it.next();

par souci de précision, notez que ce qui précède n’est pas garanti.

Documentation de l'API souligne que ce comportement ...) ne peut pas être garanti, car il est généralement impossible de donner des garanties strictes en présence de modifications simultanées non synchronisées. Les opérations de défaillance rapide lancent ConcurrentModificationException sur un base du meilleur effort ...

2
gnat

L'utilisation d'Iterators corrige également les problèmes de concurrence, tels que:

Iterator<Object> it = iterator.next().iterator();
while (it.hasNext()) {
    it.remove();
}
1
David Bemerguy

Eh bien, j’ai essayé tous les aspects de mon cas où j’étais en train d’itérer dans un adaptateur une liste, mais en raison de frapper encore et encore, je m’ai montré le message de l’exception qui avait été lancée.

 = (CopyOnWriteArraylist<MyClass>)mylist.value;

mais il m'a aussi jeté une exception de CouldNotCastException (et j'ai finalement réfléchi au fait que pourquoi utilisent-ils ou nous fournissent-ils une facalité de casting).

J'ai même utilisé le soi-disant bloc synchronisé aussi, mais cela n'a pas fonctionné ou je l'aurais peut-être mal utilisé.

Ainsi, c’est tout quand j’ai finalement utilisé le #all of time # Technique de gestion de l’exceptionin catch catch block, et cela a fonctionnéAlors placez votre code dans le

try{
//block

}catch(ConcurrentModificationException){
//thus handling my code over here
}
1