一、单线程
1. 异常情况举例
只要抛出出现异常,可以肯定的是代码一定有错误的地方。先来看看都有哪些情况会出现ConcurrentModificationException异常,下面以ArrayList remove 操作进行举例:
使用的数据集合:
List<string> myList = new ArrayList<string>(); myList.add( "1"); myList.add( "2"); myList.add( "3"); myList.add( "4"); myList.add( "5");</string></string>
以下三种情况都会出现异常:
Iterator<string> it = myList.iterator(); while (it.hasNext()) { String value = it.next(); if (value.equals( "3")) { myList.remove(value); // error } } for (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); if (value.equals( "3")) { myList.remove(value); // error } } for (String value : myList) { System. out.println( "List Value:" + value); if (value.equals( "3")) { myList.remove(value); // error } } </string></string>
异常信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
2. 根本原因
以上都有3种出现异常的情况有一个共同的特点,都是使用Iterator进行遍历,且都是通过ArrayList.remove(Object) 进行删除操作。
想要找出根本原因,直接查看ArrayList源码看为什么出现异常:
public class ArrayList<e> extends AbstractList<e> implements Cloneable, Serializable, RandomAccess { @Override public boolean remove(Object object) { Object[] a = array; int s = size; if (object != null) { for (int i = 0; i < s; i++) { if (object.equals(a[i])) { System.arraycopy(a, i + 1, a, i, --s - i); a[s] = null; // Prevent memory leak size = s; modCount++; // 只要删除成功都是累加 return true; } } } else { for (int i = 0; i < s; i++) { if (a[i] == null) { System.arraycopy(a, i + 1, a, i, --s - i); a[s] = null; // Prevent memory leak size = s; modCount++; // 只要删除成功都是累加 return true; } } } return false; } @Override public Iterator<e> iterator() { return new ArrayListIterator(); } private class ArrayListIterator implements Iterator<e> { ...... // 全局修改总数保存到当前类中 /** The expected modCount value */ private int expectedModCount = modCount; @SuppressWarnings("unchecked") public E next() { ArrayList<e> ourList = ArrayList.this; int rem = remaining; // 如果创建时的值不相同,抛出异常 if (ourList.modCount != expectedModCount) { throw new ConcurrentModificationException(); } if (rem == 0) { throw new NoSuchElementException(); } remaining = rem - 1; return (E) ourList.array[removalIndex = ourList.size - rem]; } ...... } }
List、Set、Map 都可以通过Iterator进行遍历,这里仅仅是通过List举例,在使用其他集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常。
3. 解决方案
上面列举了会出现问题的几种情况,也分析了问题出现的根本原因,现在来总结一下怎样才是正确的,如果避免遍历时进行增删操作不会出现ConcurrentModificationException异常。
// 1 使用Iterator提供的remove方法,用于删除当前元素 for (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); if (value.equals( "3")) { it.remove(); // ok } } System. out.println( "List Value:" + myList.toString()); // 2 建一个集合,记录需要删除的元素,之后统一删除 List<string> templist = new ArrayList<string>(); for (String value : myList) { if (value.equals( "3")) { templist.remove(value); } } // 可以查看removeAll源码,其中使用Iterator进行遍历 myList.removeAll(templist); System. out.println( "List Value:" + myList.toString()); // 3. 使用线程安全CopyOnWriteArrayList进行删除操作 List<string> myList = new CopyOnWriteArrayList<string>(); myList.add( "1"); myList.add( "2"); myList.add( "3"); myList.add( "4"); myList.add( "5"); Iterator<string> it = myList.iterator(); while (it.hasNext()) { String value = it.next(); if (value.equals( "3")) { myList.remove( "4"); myList.add( "6"); myList.add( "7"); } } System. out.println( "List Value:" + myList.toString()); // 4. 不使用Iterator进行遍历,需要注意的是自己保证索引正常 for ( int i = 0; i < myList.size(); i++) { String value = myList.get(i); System. out.println( "List Value:" + value); if (value.equals( "3")) { myList.remove(value); // ok i--; // 因为位置发生改变,所以必须修改i的位置 } } System. out.println( "List Value:" + myList.toString());
输出结果都是:List Value:[1, 2, 4, 5] , 不会出现异常。
以上4种解决办法在单线程中测试完全没有问题,但是如果在多线程中呢?
二、多线程
1. 同步异常情况举例
上面针对ConcurrentModificationException异常在单线程情况下提出了4种解决方案,本来是可以很哈皮的洗洗睡了,但是如果涉及到多线程环境可能就不那么乐观了。
下面的例子中开启两个子线程,一个进行遍历,另外一个有条件删除元素:
final List<string> myList = createTestData(); new Thread(new Runnable() { @Override public void run() { for (String string : myList) { System.out.println("遍历集合 value = " + string); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { for (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); System.out.println("删除元素 value = " + value); if (value.equals( "3")) { it.remove(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start();
输出结果:
遍历集合 value = 1
删除元素 value = 1
遍历集合 value = 2
删除元素 value = 2
遍历集合 value = 3
删除元素 value = 3
Exception in thread "Thread-0" 删除元素 value = 4
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at list.ConcurrentModificationExceptionStudy$1.run(ConcurrentModificationExceptionStudy.java:42)
at java.lang.Thread.run(Unknown Source)
删除元素 value = 5
结论:
上面的例子在多线程情况下,仅使用单线程遍历中进行删除的第1种解决方案使用it.remove(),但是测试得知4种的解决办法中的1、2、3依然会出现问题。
接着来再看一下JavaDoc对java.util.ConcurrentModificationException异常的描述:
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
说明以上办法在同一个线程执行的时候是没问题的,但是在异步情况下依然可能出现异常。
2. 尝试方案
(1) 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
(2) 推荐使用ConcurrentHashMap或者CopyOnWriteArrayList。
3. CopyOnWriteArrayList注意事项
(1) CopyOnWriteArrayList不能使用Iterator.remove()进行删除。
(2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);会出现如下异常:
java.lang.UnsupportedOperationException: Unsupported operation remove
at java.util.concurrent.CopyOnWriteArrayList$ListIteratorImpl.remove(CopyOnWriteArrayList.java:804)
4. 解决方案
单线程情况下列出4种解决方案,但是发现在多线程情况下仅有第4种方案才能在多线程情况下不出现问题。
List<string> myList = new CopyOnWriteArrayList<string>(); myList.add( "1"); myList.add( "2"); myList.add( "3"); myList.add( "4"); myList.add( "5"); new Thread(new Runnable() { @Override public void run() { for (String string : myList) { System.out.println("遍历集合 value = " + string); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < myList.size(); i++) { String value = myList.get(i); System.out.println("删除元素 value = " + value); if (value.equals( "3")) { myList.remove(value); i--; // 注意 } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start();
输出结果:
删除元素 value = 1
遍历集合 value = 1
删除元素 value = 2
遍历集合 value = 2
删除元素 value = 3
遍历集合 value = 3
删除元素 value = 4
遍历集合 value = 4
删除元素 value = 5
遍历集合 value = 5
OK,搞定
三、参考资料
《How to Avoid ConcurrentModificationException when using an Iterator》
相关推荐
java.util.ConcurrentModificationException 异常问题详解1
java.util.ConcurrentModificationException 解决方法 在使用iterator.hasNext()操作迭代器的时候,如果此时迭代的对象发生改变,比如插入了新数据,或者有数据被删除。 则使用会报以下异常: Java.util....
主要介绍了出现java.util.ConcurrentModificationException 问题及解决办法的相关资料,需要的朋友可以参考下
Map在遍历时候通常 现获得其键值的集合Set,然后用迭代器Iterator来对Map进行遍历。
今天小编就为大家分享一篇关于Java源码解析ArrayList及ConcurrentModificationException,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
Iterator遍历中 ConcurrentModificationException异常
java.util.ConcurrentModificationException: mutation occurred during iteration [error] scala.collection.mutable.MutationTracker$.checkMutations(MutationTracker.scala:43) [error] scala.collection....
ConcurrentModificationException异常 线程安全的集合 线程安全的 List CopyOnWriteArrayList 线程安全的Set 线程安全的Map ConcurrentHashMap ConcurrentSkipListMap java集合 线程不安全的集合 HashMap的特点 ...
NULL 博文链接:https://chenlinbo.iteye.com/blog/832335
一些关键且实用的Java开发技巧: 基础语法与规范: 始终使用public class并遵循驼峰命名法。 使用final关键字以提高性能和明确意图(不可变对象)。 明确初始化变量,避免产生未定义行为。 遵循“早初始化,晚赋值...
只读迭代:由于写时复制的机制,CopyOnWriteArraySet 的迭代器是只读的,即迭代过程中不会抛出 ConcurrentModificationException 异常。但是,迭代器获得的数据可能不包含最新的修改。 较高的内存占用:由于每次...
java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,...
50.JAVA语言如何进行异常处理,关键字:thorws,throw,try,catch,finally 51.Object类(或者其子类)的finalize()方法在什么情况下被调用? 52.一个“.java”原文件中是否可以包括多个类(不是内部类)? 53.掌握...
迭代时被修改抛出ConcurrentModificationException异常 迭代时集合被修改不抛出异常 使用原集合遍历集合元素 使用原集合的副本遍历集合元素 迭代器不要求额外的内存 迭代器需要额外的内存克隆集合对象 示例:...
Spring数据mongodb测试 在Collections.synchronizedList或Collections.synchronizedSet上测试spring数据mongodb ConcurrentModificationException
axis1.4补丁包,解决jdk1.8高并发报ConcurrentModificationException问题,该jar包重新编译jar包的一个class文件,线上环境通过
java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 9、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法...
下面的考题是我搜集的一些针对java的面试题目,还不少,大概分为[基础类][应用类][其他类],仅供参考,大家还可以说说自己的面试经历。其中另附其它常见内容,包括一些关于数据库的内容。runtime exception。 ...