【问题标题】:Concurrent Modification exception [duplicate]并发修改异常[重复]
【发布时间】:2009-09-30 05:01:19
【问题描述】:

我有一小段代码,它给了我并发修改异常。我不明白为什么我一直得到它,即使我没有看到正在执行任何并发修改。

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

【问题讨论】:

  • 最好使用 CopyOnWriteArrayList 代替 ArrayList,例如 List myList = new CopyOnWriteArrayList();在处理 CopyOnWriteArrayList 时,您可以在运行时修改列表,而迭代器可以毫无问题地迭代列表。

标签: java concurrentmodification


【解决方案1】:

为避免ConcurrentModificationException,您应该这样编写代码:

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

    }
}

java.util.ListIterator 允许您在迭代期间修改列表,但不能在创建和使用之间修改。

【讨论】:

    【解决方案2】:

    我不明白为什么我一直得到它,即使我没有看到正在执行任何并发修改。

    在创建迭代器和开始使用迭代器之间,您向要迭代的列表添加了参数。这是一个并发修改。

        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
    

    在向列表添加元素后创建迭代器:

        for (String a : args)
            s.add(a); 
    
        ListIterator<String> it = s.listIterator();  
        if (it.hasNext())
            String item = it.next();
    

    【讨论】:

      【解决方案3】:

      来自 ConcurrentModificatoinException 的 JavaDoc::“一个线程在另一个线程迭代集合时通常不允许修改集合”。

      这只是意味着如果您仍然有一个打开的迭代器,则不允许您修改列表,因为迭代器循环将中断。尝试将 ListIterator&lt;String&gt; it = s.listIterator(); 移动到 for 循环之后。

      【讨论】:

        【解决方案4】:

        在底层列表被修改后,您不能继续迭代迭代器。在这里,您在向s 添加一些项目之前创建迭代器,然后在添加后继续对其执行hasNext()next(),导致ConcurrentModificationException

        【讨论】:

          【解决方案5】:

          如果上述解决方案不能正常工作。您可以在添加新项目的同时使用旧的 for 循环来迭代 List。 请参见下面的示例:

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

          【讨论】:

          • 从现在开始我永远不会使用每个循环。使用 ListIterator 并不干净,不使用 ListIterator 可能会导致此异常。我宁愿在代码中添加更多单词并完成它。永远的旧循环:p
          • 你为什么要这样做?您应该了解您要实现的目标并为此使用最易读的语法。 for-each 和 old for 是不同的。 ConcurrentModificationException 的存在是有原因的。
          • 这种方法的问题是,由于其他线程的插入/删除,您可能会错过元素或多次看到元素。实际上,存在一个带有小窗口的竞争条件,您可能会在其中获得“索引超出范围”异常。
          • 此外,如果访问列表的代码是单线程的,那么“上述”解决方案起作用。
          • @jjz someMethod(a) 和 AClass 的代码在哪里
          【解决方案6】:

          要理解这一点,让我们看看 HashMap 实现的源代码:

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

          其中包含HashIterator如下:

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

          每次创建迭代器时:

          • 创建了一个计数器 expectedModCount,并将其设置为 modCount 的值作为入口检查点
          • modCount 在使用 put/get (add/remove) 的情况下增加
          • 迭代器的 nextEntry 方法正在检查这个值与当前的 modCount 如果它们不同,则抛出并发修改异常

          为避免这种情况,您可以:

          • 将地图转换为数组(不推荐用于大型地图)
          • 使用并发映射或列表类 (CopyOnWriteArrayList / ConcurrentMap)
          • 锁映射(这种方法消除了多线程的好处)

          这将允许您同时迭代和添加或删除元素而不会引发异常

          并发映射/列表迭代器是一个“弱一致”迭代器,它将 从不抛出 ConcurrentModificationException,并保证 遍历构造迭代器时存在的元素, 并且可能(但不保证)反映任何修改 施工后。

          More info on CopyOnWriteArrayList

          【讨论】:

            【解决方案7】:

            ConcurrentModificationException 可能在单线程环境和多线程环境中出现。 主要问题是所有通用迭代器(如 ArrayList 中使用的迭代器)都是 FailFast 迭代器,当我们尝试修改一个列表时,如果一个迭代器已经在迭代它就会失败。 解决方案 -> 如果需求需要这种情况,请使用 CopyOnWriteArrayList 而不是使用 ArrayList。

            对于一个完整的演示,可以使用下面提到的代码。 我们只需要将实现从 CopyOnWriteArrayList 更改为 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;
                }
            
            }
            

            有关更多信息,请点击此链接,这可能会很有帮助ConcurrentModificationException Java Docs

            【讨论】:

            • @Najib Arif 要获得完整的演示,可以使用下面提到的代码。我们只需要将实现从 ArrayList 更改为 CopyOnWriteArrayList (应该是这样)
            • @NajibArif 要获得完整的演示,可以使用下面提到的代码。我们只需要将实现从 ArrayList 更改为 CopyOnWriteArrayList (应该是这样)
            【解决方案8】:

            这不起作用:

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

            这行得通:

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

            【讨论】:

            • 你应该解释原因,它比提供解决方案更有帮助
            【解决方案9】:

            看看 oracle documentation 页面。

            public class ConcurrentModificationException
            extends RuntimeException
            

            当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常

            请注意,此异常并不总是表明对象已被不同的线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。 例如,如果线程在使用 fail-fast 迭代器迭代集合时直接修改集合,则迭代器将抛出此异常

            在您的情况下,您在创建迭代器后修改了集合,因此遇到了异常。

            如果您按照 Stephen C 的回答更改代码,则不会出现此错误。

            【讨论】:

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