并发修改例外

时间:2009-09-30 05:01:19

标签: java concurrentmodification

我有这段小代码,它给了我并发修改异常。我无法理解为什么我会继续得到它,即使我没有看到任何同时进行的修改。

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

9 个答案:

答案 0 :(得分:51)

要避免使用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允许您在迭代期间修改列表,但不能在创建和使用它之间修改列表。

答案 1 :(得分:44)

  

我无法理解为什么我会继续得到它,即使我没有看到任何同时进行的修改。

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

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

答案 2 :(得分:11)

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

它只是意味着如果你仍然有一个打开的迭代器,则不允许修改列表,因为迭代器循环会中断。尝试移动ListIterator<String> it = s.listIterator();直到for循环。

答案 3 :(得分:8)

修改基础列表后,不允许继续迭代迭代器。在这里你先创建迭代器,然后再向s添加几个项目,然后在添加之后继续对其进行hasNext()next(),导致ConcurrentModificationException < / p>

答案 4 :(得分: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));
        }


    }
}

答案 5 :(得分:3)

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

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

答案 6 :(得分:1)

这不起作用:

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

答案 7 :(得分:1)

理解这一点让我们看看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(添加/删除)
  • 的情况下递增
  • nextEntry 迭代器的方法是使用当前的modCount检查此值,如果它们是不同的并发修改异常则抛出

为了避免这种情况你可以:

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

这将允许您同时迭代并添加或删除元素而不会出现异常

  

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

More info on CopyOnWriteArrayList

答案 8 :(得分:1)

查看oracle documentation页面。

public class ConcurrentModificationException
extends RuntimeException
  

当不允许进行此类修改时,检测到并发修改对象的方法可能抛出此异常

     

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

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

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