【问题标题】:Concurrent Modification Exception : adding to an ArrayList并发修改异常:添加到 ArrayList
【发布时间】:2011-10-15 12:37:02
【问题描述】:

问题发生在

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&lt;Element&gt;

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

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

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

 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)

我怎样才能做到这一点?

【问题讨论】:

标签: java android exception arraylist iteration


【解决方案1】:

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

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

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

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

【讨论】:

  • 其实我一开始有增强的for循环。我最近刚刚使用了一个迭代器,因为我认为这有助于解决问题,但事实并非如此。
  • 我相信增强的 for 循环在后台使用了一个迭代器,因此使用它应该会导致同样的问题。
  • @OWiz:这两种形式将编译成基本相同的代码。
【解决方案2】:

ConcurrentModificationException 发生在您在使用Iterator 遍历列表时修改列表(通过添加或删除元素)。

试试

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 的建议考虑对每个循环进行增强。

【讨论】:

  • 当我不得不使用从列表中删除时不起作用,而是传统的 for 循环我必须每次都在循环中修改“i”。
  • @AmitTumkur 尝试使用“iterator.remove();”而不是“list.remove(object)”:)
【解决方案3】:

在这种情况下从列表中添加会导致 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

【讨论】:

    【解决方案4】:

    索引 for 循环也应该可以工作。

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

    【讨论】:

    • 你可能会陷入无限循环。
    【解决方案5】:

    使用迭代器还可以解决并发问题,如下所示:

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

    【讨论】:

    • 迭代器只有在你想删除元素时才有用,但如果你想按照这个问题的要求将项目添加到列表中,它们就没有用了。
    【解决方案6】:

    我通常使用这样的东西:

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

    快速、干净且无错误

    另一种选择是使用 CopyOnWriteArrayList

    【讨论】:

    • 但是new Arraylist的对象会执行list的浅拷贝,指向同一个list。
    • 不,它不会指向同一个列表。列表被复制。
    • 但这不会遍历具有不同项目数的列表吗?我想,如果你从原始列表中删除一个项目,它仍然在复制的 ArrayList 上,不是吗?
    • 修改后的列表中的item数量显然会有所不同。关键是您仍然可以遍历所有原始列表项,同时根据需要修改列表。
    【解决方案7】:

    好吧,在我在适配器中迭代列表的情况下,我已经尝试了所有方面,但由于一次又一次地点击,我向我展示了抛出异常的消息。 我尝试将列表投射到

     = (CopyOnWriteArraylist<MyClass>)mylist.value;
    

    但它也给我抛出了一个 CanNotCastException 异常,(我终于思考了一个事实,即他们为什么使用或为我们提供铸造方法)。

    我什至也使用了所谓的同步块,但即使它也不起作用,或者我可能会以错误的方式使用它。

    因此,当我最终使用#all of time# 处理异常的技术时,一切都结束了 在 try catch 块中,它起作用了 所以把你的代码放在

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

    【讨论】:

      【解决方案8】:

      我解决了创建锁(Kotlin):

      import java.util.concurrent.locks.ReentrantLock
      
      Class A {
          private val listLock = ReentrantLock()
          fun doSomething(newElement){
              listLock.lock()
              list.add(newElement)
              listLock.unlock()
          }
      }
      

      【讨论】:

        【解决方案9】:

        您可以使用自动递减for 循环,并在下次处理额外的元素。

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

        【讨论】:

          【解决方案10】:

          公认的解决方案(创建集合的副本)通常效果很好

          但是,如果Element 包含另一个集合,这不会生成深层副本

          例子:

          class Element {
             List<Kid> kids;
          
             getKids() {
                return kids;
             }
          }
          

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

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

          如果您迭代 element.getKids() 并同时更改该元素的 kids,您仍然可以获得 ConcurrentModificationException

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

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

          【讨论】:

            猜你喜欢
            • 2014-10-08
            • 1970-01-01
            • 1970-01-01
            • 2012-12-24
            • 1970-01-01
            • 2016-11-19
            相关资源
            最近更新 更多