Java在操作ArrayList、HashMap、TreeMap等容器类时,遇到了java.util.ConcurrentModificationException异常。以ArrayList为例,如下面的代码片段:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; public class Test { public static void main(String[] args){
List<String> strList = new ArrayList<String>();
strList.add("string1");
strList.add("string2");
strList.add("string3");
strList.add("string4");
strList.add("string5");
strList.add("string6"); // 操作方式1:while(Iterator);报错
Iterator<String> it = strList.iterator();
while(it.hasNext()) {
String s = it.next();
if("string2".equals(s)) {
strList.remove(s);
}
} // 解决方案1:使用Iterator的remove方法删除元素
// 操作方式1:while(Iterator):不报错
// Iterator<String> it = strList.iterator();
// while(it.hasNext()) {
// String s = it.next();
// if("string2".equals(s)) {
// it.remove();
// }
// } // 操作方式2:foreach(Iterator);报错
// for(String s : strList) {
// if("string2".equals(s)) {
// strList.remove(s);
// }
// } // 解决方案2:不使用Iterator遍历,注意索引的一致性
// 操作方式3:for(非Iterator);不报错;注意修改索引
// for(int i=0; i<strList.size(); i++) {
// String s = strList.get(i);
// if("string2".equals(s)) {
// strList.remove(s);
// strList.remove(i);
// i--;// 元素位置发生变化,修改i
// }
// } // 解决方案3:新建一个临时列表,暂存要删除的元素,最后一起删除
// List<String> templist = new ArrayList<String>();
// for (String s : strList) {
// if(s.equals("string2")) {
// templist.add(s);
// }
// }
// // 查看removeAll源码,其使用Iterator进行遍历
// strList.removeAll(templist); // 解决方案4:使用线程安全CopyOnWriteArrayList进行删除操作
// List<String> strList = new CopyOnWriteArrayList<String>();
// strList.add("string1");
// strList.add("string2");
// strList.add("string3");
// strList.add("string4");
// strList.add("string5");
// strList.add("string6");
// Iterator<String> it = strList.iterator();
// while (it.hasNext()) {
// String s = it.next();
// if (s.equals("string2")) {
// strList.remove(s);
// }
// } }
}

执行上述代码后,报错如下:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at concurrentModificationException.Test.main(Test.java:21)

在第21行报错,即it.next(),迭代器在获取下一个元素时报错。找到java.util.ArrayList第830行,看到it.next()的源代码,如下:

        @SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

调用了checkForComodification()方法,代码如下:

        final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

即,比较modCount和expectedModCount两个是否相等。modCount是ArrayList从AbstractList继承来的属性,查看modCount属性的doc文档,可知,modCount表示列表(list)被结构性修改(structurally modified)的次数。structurally modified是指造成列表中元素个数发生变化的操作,ArrayList中的add,addAll,remove, fastRemove,clear, removeRange,  ensureCapacity,  ensureCapacityInternal,  ensureExplicitCapacity等方法都会使modCount加1,而batchRemove,removeAll,retainAll等方法则根据删除的元素数增加modCount的值(removeAll和retainAll都是调用batchRemove实现,具体modCount的修改算法还需研究)。

第一个代码片段中的操作方式1和操作方式2都是采用了迭代器的方式。当使用iterator方法得到Iterator对象(或者使用listIterator获得ListItr对象),其实是返回了一个Iterator接口的实现类ArrayList$Itr(继承自AbstractList$Itr),该类为ArrayList的一内部类,该类中有一个expectedModCount字段,当调用ArrayList$Itr的next方法时,会先检查modCount的值是否等于expectedModCount的值(其实在调用next, remove, previous, set, add等方法时都会检查),不相等时就会抛出java.util.ConcurrentModificationException异常。这种现象在java doc中称作fail-fast。

为什么会抛出该异常呢?从代码可以看到调用了6次add方法,这时modCount的值也就为6,当当使用iterator方法得到Iterator对象时把modCount的值赋给了expectedModCount,开始时expectedModCount与modCount是相等的,当迭代到第二个元素(index=1)“string2”时,因为if条件为true,于是又调用了remove方法,调用remove方法时modCount值又加1,此时modCount的值为7了,而expectedModCount的值并没有改变,当再次调用ArrayList$Itr的next方法时检测到modeCount与expectedModCount不相等了,于是抛出异常。

当把if语句写成if(s.equals("string5"))时又没有抛出该异常,这又是为什么呢?ArrayList$Itr中还有一个名为cursor的字段用来指向迭代时要操作的元素索引,初始值为0,每调用一次next方法该字段值加1,注意是先从集合中取出了元素再加1的。当判断"string5"时,注意是倒数第二个元素,这些cursor的值为5,移除掉元素"string5"时,List的size为5,当调用ArrayList$Itr的hasNext方法判断有无下一个元素时,判断的依据为cursor的值与size是否相等,不相等则还有下一个元素,而此时两者值刚好相等,也就没有往下执行next方法了,也就没有抛出异常,因此删掉倒数第二个元素时不会抛异常的异常。

解决方案有四种,直接看第一段代码即可。

参考链接:

http://blog.csdn.net/xtayfjpk/article/details/8451178

《多线程情况下只能使用CopyOnWriteArrayList》http://www.2cto.com/kf/201403/286536.html

java.util.ConcurrentModificationException异常分析的更多相关文章

  1. java.util.ConcurrentModificationException 异常问题详解

    环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...

  2. java.util.ConcurrentModificationException异常原因及解决方法

    在java语言中,ArrayList是一个很常用的类,在编程中经常要对ArrayList进行删除操作,在使用remove方法对ArrayList进行删除操作时,报java.util.Concurren ...

  3. java集合--java.util.ConcurrentModificationException异常

    ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...

  4. Java处理java.util.ConcurrentModificationException异常

    代码: public static void reduce(HashMap<String, Integer> hashMap, final Integer count) { Iterato ...

  5. java.util.ConcurrentModificationException异常排查

      java.util.ConcurrentModificationException对于这个异常我们一般会认为是在遍历list的时候对这个list做了add,remove等修改操作造成的,最近在线上 ...

  6. java.util.ConcurrentModificationException 异常解决的方法及原理

    近期在修程序的bug,发现后台抛出下面异常: Exception in thread "main" java.util.ConcurrentModificationExceptio ...

  7. java.util.ConcurrentModificationException异常的解决

    问题复现: List<String> list = new ArrayList<>();list.add("11");list.add("55&q ...

  8. java.util.ConcurrentModificationException异常;java.util.ConcurrentModificationException实战

    写代码遇到这个问题,很多博客文章都是在反复的强调理论,而没有对应的实例,所以这里从实例出发,后研究理论: 一.错误产生情况 1 .字符型 (1)添加 public static void main(S ...

  9. java.util.ConcurrentModificationException 异常原因和解决方法

    不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁. 注意: 1.foreach遍历 ...

随机推荐

  1. java并发编程:线程安全管理类--原子操作类--AtomicBoolean

    1.类AtomicBoolean

  2. 根据马甲、应用商店、统计每天的注册量,要求可以根据选择马甲和app,马甲和appstrore和user_login不同表问题

    这个马甲属于一个表,appStore另一张表,用户登录表,主要操作的就是这三个表. 我这里的马甲和app的id都与用户登录表中的channel对应,在channel存放的是majiaId + “|” ...

  3. Go安装一些第三方库

    原文链接:https://javasgl.github.io/go-get-golang-x-packages/ 侵权联系删除! go在go get 一些 package时候的会由于众所周知的原因而无 ...

  4. HTML5和XHTML的区别

    既然被问到了HTML5和XHTML的区别,那我就在这里给大家分享一些我个人的理解,同时我也觉得从他们的来源上讲,他们藏着一个有趣的故事. 首先认识三个组织,IETF (Internet Enginee ...

  5. Ubuntu tar方式安装mysql5.7.21 时报错 [ERROR] Can't locate the language directory. 以及 ------ libaio.so.1: cannot open shared object file

    参考帖子: http://blog.csdn.net/ty0415/article/details/22958133 首先,在 MySQL 官方网站上下载安装包, 如图: 然后,执行安装命令 bin/ ...

  6. form表单传递对象数组

    ajax传递数组.form表单提交对象数组 在JSP页面开发中,我们常常会用到form表单做数据提交,由于以前一直只是使用 form表单提交单个对象,只要表单文本域的name值和接收的对象的属性名一致 ...

  7. table 如何给tr border颜色

    border-collapse属性值 说明 separate 默认值,边框分开,不合并 collapse 边框合并,如果相邻,则共用一个边框 table,th,td{border:1px solid  ...

  8. List用法与介绍

     泛型的好处:它为使用c#语言编写面向对象程序增加了极大的效力和灵活性.不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高.      性能注意事项:在决定使用ILis ...

  9. 关于mybatis中一级缓存和二级缓存的简单介绍

    关于mybatis中一级缓存和二级缓存的简单介绍 mybatis的一级缓存: MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候 ...

  10. jmap打dump异常

    背景 用jmap打dump文件经常遇到如下异常,打不出来,哥今天告拆大家一个终极解决方法,嘘,不要告拆别人.. Attaching to core -F from executable 421442, ...