并发修改例外:添加到ArrayList

时间:2011-07-28 21:53:31

标签: java android exception arraylist iteration

问题发生在

Element element = it.next();

此代码包含该行,位于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;

        }           
    }
}

所有这些都在synchronized(mElements)内,其中mElementsArrayList<Element>

当我触摸Element时,它可能会激活cFlag,这将创建另一个具有不同属性的Element,这些属性将从屏幕上掉落并在不到一秒的时间内自行消失。这是我创建粒子效果的方式。我们可以称之为“粒子”crack,就像构造函数中的String参数一样。

这一切都正常,直到我添加另一个主Element。现在我同时在屏幕上有两个Elements,如果我触摸最新的Element,它可以正常工作,并启动粒子。

但是,如果我触摸并激活旧cFlag上的Element,那么它会给我例外。

 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)

我该如何做到这一点?

10 个答案:

答案 0 :(得分:61)

在使用Iterator遍历列表时修改列表(通过添加或删除元素)时会出现

ConcurrentModificationException

尝试

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 );

另外,你应该考虑Jon建议的每个循环的增强功能。

答案 1 :(得分:41)

我通常使用这样的东西:

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

快速,干净,无错误

另一种选择是使用CopyOnWriteArrayList

答案 2 :(得分:20)

在迭代它时,不允许向集合中添加条目。

一种选择是在迭代List<Element>时为新条目创建新的mElements,然后将所有新条目添加到mElement之后(mElements.addAll(newElements) )。当然,这意味着你不会为这些新元素执行循环体 - 这是一个问题吗?

同时,我建议您更新代码以使用enhanced for loop

for (Element element : mElements) {
    ...
}

答案 3 :(得分:12)

索引for循环也应该有效。

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

答案 4 :(得分:2)

在这种情况下从列表中添加会导致CME,synchronized的任何数量都不会让您避免这种情况。相反,请考虑使用迭代器添加...

        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;

                }           
            }
        }

此外,我认为它有点表示如......

  

......问题发生在Element element = it.next();

为了精确注意,上述情况无法保证。

API documentation指出这种 ...行为无法保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬保证。失败快速操作会在尽力而为的基础上抛出ConcurrentModificationException ...

答案 5 :(得分:1)

使用迭代器也可以修复并发问题,如下所示:

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

答案 6 :(得分:1)

我已经在我的情况下尝试了所有方面,我在一个适配器中迭代一个列表但是由于一次又一次地打击我向我展示了异常被抛出的消息。 我尝试将列表转换为

 = (CopyOnWriteArraylist<MyClass>)mylist.value;

但它也给我一个例外的CanNotCastException,(我终于思考了为什么他们使用或为我们提供铸造的方法)。

我甚至也使用了所谓的同步块,但即使它没有用,否则我可能会以错误的方式使用它。

因此,当我最终使用#all of time#Technique处理异常时,就是这样 在try catch块中,它工作 所以把你的代码放在

try{
//block

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

答案 7 :(得分:1)

您可以使用自动减量for循环,并在下次处理其他元素。

List additionalElements = new ArrayList();
for(int i = mElements.size() - 1; i > -1 ; i--){
    //your business
    additionalElements.add(newElement);
}
mElements.add(additionalElements);

答案 8 :(得分:1)

通常接受的解决方案(用于创建馆藏副本)效果很好

但是,如果Element 包含另一个收藏夹,则不会产生深拷贝

示例:

class Element {
   List<Kid> kids;

   getKids() {
      return kids;
   }
}

现在,当您创建元素列表的副本时:

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

如果您在ConcurrentModificationException上进行迭代并相应地更改了该元素的element.getKids(),您仍然可以获得kids

回想起来很明显,但是我最终进入了这个线程,所以也许这个提示对其他人也有帮助:

class Element {
   List<Kid> kids;

   getKids() {
      // Return a copy of the child collection
      return new ArrayList<Kid>(kids);
   }
}

答案 9 :(得分:0)

我解决了创建锁的问题(Kotlin):

^                   # beginning of line
  [a-z0-9]+         # 1 or more alphanum
  (?:               # start non capture group
    [._-]           # period, underscore or hyphen
    [a-z0-9]+       # 1 or more alphanum
  )*                # end group, may appear 0 or more times
$