java.util.ConcurrentModificationException异常分析
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异常分析的更多相关文章
- java.util.ConcurrentModificationException 异常问题详解
环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...
- java.util.ConcurrentModificationException异常原因及解决方法
在java语言中,ArrayList是一个很常用的类,在编程中经常要对ArrayList进行删除操作,在使用remove方法对ArrayList进行删除操作时,报java.util.Concurren ...
- java集合--java.util.ConcurrentModificationException异常
ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...
- Java处理java.util.ConcurrentModificationException异常
代码: public static void reduce(HashMap<String, Integer> hashMap, final Integer count) { Iterato ...
- java.util.ConcurrentModificationException异常排查
java.util.ConcurrentModificationException对于这个异常我们一般会认为是在遍历list的时候对这个list做了add,remove等修改操作造成的,最近在线上 ...
- java.util.ConcurrentModificationException 异常解决的方法及原理
近期在修程序的bug,发现后台抛出下面异常: Exception in thread "main" java.util.ConcurrentModificationExceptio ...
- java.util.ConcurrentModificationException异常的解决
问题复现: List<String> list = new ArrayList<>();list.add("11");list.add("55&q ...
- java.util.ConcurrentModificationException异常;java.util.ConcurrentModificationException实战
写代码遇到这个问题,很多博客文章都是在反复的强调理论,而没有对应的实例,所以这里从实例出发,后研究理论: 一.错误产生情况 1 .字符型 (1)添加 public static void main(S ...
- java.util.ConcurrentModificationException 异常原因和解决方法
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁. 注意: 1.foreach遍历 ...
随机推荐
- python学习笔记(二)---编辑工具sublimeText3运行python
转载地址:https://blog.csdn.net/Maek_Tyx/article/details/76933897 1. 打开Sublime text 3 安装package controlSu ...
- HDU 1029 基础dp
题目链接:Ignatius and the Princess IV 大意:就是在N个数里找出唯一一个至少出现过(N+1)/ 2 次的数. 1 <= N <= 999999. hash: / ...
- [批处理]Oracle启动助手
前段日子开始学Oracle数据库,但是由于Oracle数据库的服务启动时间很长 所以机房的里面所有电脑的Oracle服务全部是被禁用的 所以每次上机使用的时候都要先进服务管理,然后把禁用更改为手动模式 ...
- 流程设计器jQuery + svg/vml(Demo2 - UI界面增加属性显示)
设计器UI界面有了,接下来结点的属性怎么显示呢,采用弹窗的话觉得不方便用户:用easyui的propertygrid在最右边显示,又觉得要引入easyui,使得插件变复杂了:最后决定自己写. 1.实现 ...
- 【转】MongoDB 3.0 正式版本即将发布,强力推荐
MongoDB 今天宣布3.0 正式版本即将发布.这标志着 MongoDB 数据库进入了一个全新的发展阶段,提供强大.灵活而且易于管理的数据库管理系统. MongoDB 3.0 在性能和伸缩性方面都有 ...
- 前端打印日志到localStroge并导出
interface LogEntry { data: any time: Date } export class PersistantLog { //最大条数 maxEntries = 3000; i ...
- Tcl 和 Raft 发明人的软件设计哲学
John Ousterhout(斯坦福大学教授,Tcl 语言.Raft 协议的发明人...真的是超级牛人,Title 好多好多,这里就列几个大家熟悉的),在 Google 做了一次演讲,题目就叫 「A ...
- 大数据框架hadoop的序列化机制
Java内建序列化机制 在Windows系统上序列化的Java对象,可以在UNIX系统上被重建出来,不需要担心不同机器上的数据表示方法,也不需要担心字节排列次序. 在Java中,使一个类的实例可被序列 ...
- SpringCloud初体验:二、Config 统一配置管理中心
Spring Cloud Config : 配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储.Git以及Subversion. 配置中心也区分为服务端和客户端,本次体 ...
- CentOS 7 安装Memcached服务
Memcached 简介 Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站 ...