并发修改exception
我有这一小段代码,它给了我并发修改exception。 我不明白为什么我一直这样做,即使我没有看到任何并发的修改正在进行。
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); } }
为了避免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
允许您在迭代过程中修改列表,但不能在创build和使用它之间进行修改。
我不明白为什么我一直这样做,即使我没有看到任何并发的修改正在进行。
在创build迭代器并开始使用迭代器之间,您将参数添加到要迭代的列表中。 这是一个并发修改。
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
在完成添加元素到列表之后创build迭代器:
for (String a : args) s.add(a); ListIterator<String> it = s.listIterator(); if (it.hasNext()) String item = it.next();
从JavaDoc: for ConcurrentModificatoinException:“一个线程修改一个集合,而另一个线程迭代它通常是不可能的”。
它只是意味着如果你仍然有一个开放的迭代器,你不能修改列表,因为迭代器循环会中断。 尝试移动ListIterator<String> it = s.listIterator();
直到for循环之后。
在修改底层列表后,您不能继续迭代迭代器。 在这里,在向s
添加一些项目之前创build迭代器,然后在添加之后继续执行hasNext()
和next()
,导致ConcurrentModificationException
如果上述解决scheme无法正常工作。 您可以使用旧的for-loop迭代列表,同时添加新的项目。 看下面的例子:
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)); } } }
ConcurrentModificationException 可能出现在单线程环境和multithreading环境中 。 主要的问题是所有的通用迭代器(比如ArrayList中使用的迭代器 )都是FailFast迭代器 ,当我们尝试修改一个列表时,如果一个迭代器已经迭代了这个迭代器 ,它就会失败。 解决scheme – >使用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; } }
对于更多inifo请按照此链接可能会有所帮助ConcurrentModificationException Java文档
这不起作用:
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();
了解这个让我们看一下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(); .... }
每次你创build一个迭代器:
- 将创build一个计数器expectedModCount ,并将其设置为modCount的值作为条目检查点
- 在使用put / get(add / remove)的情况下,modCount递增
- 迭代器的nextEntry方法是用当前modCount检查这个值,如果它们是不同的并发修改exception抛出
避免这个ü可以:
- 将地图转换为数组(不build议用于大型地图)
- 使用并发映射或列表类( CopyOnWriteArrayList / ConcurrentMap )
- locking映射(这种方法消除了multithreading的好处)
这将允许您迭代并添加或删除元素,而不会引发exception
并发映射/列表迭代器是一个“弱一致”的迭代器,它永远不会抛出ConcurrentModificationExceptionexception,并保证遍历构造迭代器时存在的元素,并可能(但不能保证)反映构造之后的任何修改。
有关CopyOnWriteArrayList的更多信息
看看oracle 文档页面。
public class ConcurrentModificationException extends RuntimeException
当这种修改不被允许时,可以通过检测到对象的并发修改的方法抛出该exception
请注意,这个exception并不总是表明一个对象已经被不同的线程同时修改。 如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此exception。 例如,如果一个线程使用快速迭代器迭代集合的时候直接修改了一个集合,迭代器将抛出这个exception 。
在你的情况下,你已经在创build迭代器后修改了集合,因此遇到了exception。
如果你按照Stephen C的回答改变你的代码,你将不会得到这个错误。