一、前言  

  Java中,集合类ArrayList不管是在开发工作中,还是在面试中,都应该是个比较高频出现的知识点。在使用过程中,可能会遇到迭代删除的需求场景,此时如果代码书写不当,极有可能会抛出 java.util.ConcurrentModificationException 异常信息。下面对这个异常做点分析,为什么会出现异常,怎样去正确的迭代删除。

二、异常原因分析

  测试代码如下:

package com.cfang.prebo.oTest;

import java.util.Iterator;
import java.util.List; import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists; public class TestListException { public static void main(String[] args) {
List<Integer> list = Lists.newArrayList();
list.add(1);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
Integer val = iterator.next();
if(val == 1) {
list.remove(val);
// iterator.remove();
}
}
System.out.println("result:" + JSON.toJSONString(list));
}
}

  运行结果:

  从异常栈信息中可以看出,最终抛出此异常的方法,是 checkForComodification 方法。下面进行追根逐源的看看,为什么方法会抛出异常。

  首先整体贴出迭代器 Iterator 对 List 进行迭代的关键性代码片段:

public Iterator<E> iterator() {
return new Itr();
} /**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @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];
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

  对List的迭代iterator会new出个Itr对象的引用,Itr是个成员内部类实现。其中几个关键性的属性:

    cursor - 游标索引,表示下一个可访问的元素的索引

    lastRet - 还是索引,是上一个元素的索引。默认值-1

    expectedModCount - 对集合的修改期望值,初始值等于modCount

    modCount的定义在AbstractList中,初始值为0,如下定义:

protected transient int modCount = 0;

    该值会在List的方法add以及remove中,进行加1操作,如下代码片段:

public E remove(int index) {
rangeCheck(index); modCount++;
E oldValue = elementData(index); int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

  

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

  也就是说,在进行add和remove的时候,都会将modCount值修改。

  好了,铺垫到这里,就可以来结合测试main方法来进行一步步的解释说明了:

    1、初始化ArrayList,调用list.add方法,此时,modCount=1,list.size = 1

    2、初始化itreator迭代循环。此时,expectedModCount = modCount = 1,cursor默认值0,lastRet默认值-1

    3、itreator.hasNext方法判断,cursor != size成立,有元素可访问,进入循环

    4、调用itreator.next方法,校验后获取值。此时expectedModCount == modCount成立,校验通过。获取值并设置相关属性 lastRet = 0,cursor = 1

    5、调用list.remove方法,modCount加1。此时,modCount=2,list.size = 0

    6、itreator.hasNext方法判断,cursor != size成立,进入循环

    7、调用itreator.next方法,校验方法checkForComodification,此时,expectedModCount != modCount成立,抛出ConcurrentModificationException异常

  写到这里,基本上为啥会出现异常,应该是已经非常明了清晰了。总结起来就是:如果是使用list.remove的话,会导致expectedModCount != modCount条件成立,也即两个的值会不等。当然了,使用for-each迭代也是一样的,毕竟for-each底层如果是对集合遍历的话,也还是利用itreator去做的。

  说完原因呢,下面简单说说解决办法:

  单线程情况下:可以使用迭代器itreator提供的remove,从源码中可以看出,在方法中会对cursor、lastRet重设值,将expectedModCount重新设值为modCount。

  多线程情况下:1、迭代删除使用锁 - synchronized或者lock

         2、创建安全的容器 - Collections.synchronizedList方法、CopyOnWriteArrayList

List之ConcurrentModificationException异常的更多相关文章

  1. Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  2. Java并发编程:Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

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

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

  4. 【转】Java ConcurrentModificationException 异常分析与解决方案--还不错

    原文网址:http://www.2cto.com/kf/201403/286536.html 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会 ...

  5. 【转】ConcurrentModificationException异常解决办法 --不错

    原文网址:http://blog.sina.com.cn/s/blog_465bcfba01000ds7.html 1月30日java.util.ConcurrentModificationExcep ...

  6. 【转】Java ConcurrentModificationException异常原因和解决方法

    原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...

  7. ConcurrentModificationException异常解决办法

    今天在写一个带缓存功能的访问代理程序时出现了java.util.ConcurrentModificationException异常,因为该异常是非捕获型异常而且很少见,所以费了些时间才找到问题所在,原 ...

  8. 修改List报ConcurrentModificationException异常原因分析

    使用迭代器遍历List的时候修改List报ConcurrentModificationException异常原因分析 在使用Iterator来迭代遍历List的时候如果修改该List对象,则会报jav ...

  9. java中的ConcurrentModificationException异常

    先看这样一段代码: List<String> list = new ArrayList<String>(); list.add("1"); list.add ...

  10. (转)Java ConcurrentModificationException异常原因和解决方法

    转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...

随机推荐

  1. JavaScript算法实现——排序

    在计算机编程中,排序算法是最常用的算法之一,本文介绍了几种常见的排序算法以及它们之间的差异和复杂度. 冒泡排序 冒泡排序应该是最简单的排序算法了,在所有讲解计算机编程和数据结构的课程中,无一例外都会拿 ...

  2. Codeforces 1009D

    题意略. 思路: 可知对于一个拥有n个点的图来说,它至少需要有n - 1条边来维持连通性,而且数字1恰好与后面的n - 1个数字互质: 至于n个点的图可以产生合法的互质边的个数的上限,我们可以通过莫比 ...

  3. Nginx介绍与反向代理

    Nginx的产生 没有听过Nginx?那么一定听过它的"同行"Apache吧!Nginx同Apache一样都是一种WEB服务器.基于REST架构风格,以统一资源描述符(Unifor ...

  4. C++ 并发编程之互斥锁和条件变量的性能比较

    介绍 本文以最简单生产者消费者模型,通过运行程序,观察该进程的cpu使用率,来对比使用互斥锁 和 互斥锁+条件变量的性能比较. 本例子的生产者消费者模型,1个生产者,5个消费者. 生产者线程往队列里放 ...

  5. 关于工作流引擎ccflow待办分类 研究与技术实现

    关于工作流引擎待办分类 研究与技术实现 关键字:工作流引擎 BPM系统 待办类型 名词:待办 概要介绍:待办就是当前的登录人员要处理的工作,在工作流程里面的节点类型不同,业务场景不同,我们把待办分为如 ...

  6. SCRUM术语

    http://www.scrumcn.com/agile/scrum-knowledge-library/scrum.html#tab-id-2 Scrum: Scrum无对应中文翻译 Agile: ...

  7. 洛谷P1273 有线电视网 树上分组背包DP

    P1273 有线电视网 )逼着自己写DP 题意:在一棵树上选出最多的叶子节点,使得叶子节点的值 减去 各个叶子节点到根节点的消耗 >= 0: 思路: 树上分组背包DP,设dp[u][k] 表示 ...

  8. CodeForces 931E Game with String

    Game with String 题意:有一个字符串,可以选择从第K位开始,将[K,len(s)-1]的字符都移到前面去,现在给你一个首字母,你可以再选择一位进行观察,然后猜测这个K的值是多少, 现在 ...

  9. yzoj1985 最长公共单调上升子序列 题解

    题面给两个序列a,b长度分别为n,m求最长公共上升子序列,百度了一下求公共子序列的问题好像叫做LCS,而上升的叫做LCIS.都是dp的例题. 先来说说最长公共子序列,这是一道比较经典的dp题,我们可以 ...

  10. Nginx使用GeoIP模块来限制地区访问

    举例比如限制泰国地区的IP访问: 前提条件,安装了http geoip 或stream geoip模块的Nginx Plus或者开源nginx Maxmind的GeoLite Legacy数据库 1. ...