并发修改例外

问题描述 投票:42回答:9

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

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);
    }
}
java concurrentmodification
9个回答
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允许您在迭代期间修改列表,但不能在创建和使用它之间修改列表。


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

11
投票

来自JavaDoc:的ConcurrentModificatoinException:“一个线程修改一个集合而另一个线程迭代它时通常不可能”。

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


8
投票

修改基础列表后,不允许继续迭代迭代器。在这里你创建迭代器,然后向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));
        }


    }
}

3
投票

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

}

欲了解更多信息,请点击此链接,这可能对qazxsw poi很有帮助


1
投票

这不起作用:

ConcurrentModificationException Java Docs

这有效:

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

1
投票

要了解这一点,我们来看看HashMap实现的来源:

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

其中包含HashIterator如下:

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

每次创建迭代器时:

  • 创建一个计数器expectedModCount,并将其设置为modCount的值作为入口检查点
  • 在使用put / get(添加/删除)的情况下,modCount会递增
  • 迭代器的nextEntry方法是使用当前modCount检查此值,如果它们是不同的并发修改异常是throw

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

  • 将地图转换为数组(不建议用于大型地图)
  • 使用并发映射或列表类(private abstract class HashIterator { ... int expectedModCount = modCount; ... HashMapEntry<K, V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); .... } / CopyOnWriteArrayList
  • 锁定映射(这种方法消除了多线程的好处)

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

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

ConcurrentMap


1
投票

看看oracle More info on CopyOnWriteArrayList 页面。

documentation

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

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

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

如果您根据public class ConcurrentModificationException extends RuntimeException 答案更改代码,则不会出现此错误。

© www.soinside.com 2019 - 2024. All rights reserved.