摘要:
为了保证线程安全,在迭代器迭代的过程中,线程是不能对集合本身进行操作(修改,删除,增加)的,否则会抛出ConcurrentModificationException的异常。 
示例:
  public static void main(String[] args) {
Collection num = new ArrayList<String>();
num.add("One");
num.add("Two");
num.add("Three"); Iterator<String> iterator= num.iterator();
while(iterator.hasNext()){
String element = iterator.next();
if("One".equals(element)){
num.remove(element);
}else {
System.out.println(element);
}
}
}
}
运行结果:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.mobin.thread.IterationTest.main(IterationTest.java:20) //错误代码对应上面的11行
源码分析
先来分析下整个代码的执行流程:
 
集合操作中有两个比较重要的指标:modCount和exceptedModCount。
 
modCount值是用来记录当前集合被修改的次数,每修改一次就进行加1(可以认为为集合的版本号),而exceptedModCount是在iterator初始化是就已经指定的值,值为exceptedModCount = modCount,对于上面的代码,开始就对集合进行了三次add操作,所以modCount=3,当代码执行到第9行时就创建了iterator对象执行exceptedModCount = modCount语句对exceptedModCount进行了赋值操作,此时exceptedModCount=3,具体过程如下:
当执行到while语句时,会对iterator.hasnext()进行判断真假,hasnext访方法实现如下:
public boolean hasNext() {
return cursor != size;
}
cursor又是什么呢?
     cursor是访问集合时指向元素的游标,开始为0,每执行next语句一次就+1,所以当访问到最后一个元素时cursor就等于size,此时就会结束循环。
之后执行next语句,next具体实现如下:
  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];
}
接着代码进入if语句执行了remove操作,modCount的值+1,这时再一次循环while语句进入next语句并执行next里的checkForComodifcation方法对modCount和exceptedModCount的值进行判断,此时modCount!= exceptedModCount就抛出了开始的那个异常,这就是报错的原因。
 
当把if语句里的条件改下面代码又会怎样呢?
if("Two".equals(element)){ //条件发生了改变,对第二个元素进行判断
num.remove(element);
}else {
System.out.println(element);
}
运行结果:
One
只输出了一个值就结束程序了,并没有报错!
 
当执行第一遍while语句时,执行了next方法,cursor值+1,cursor=1,并输出One
 
当执行第二遍while语句时,执行了next方法,cursor值+1,cursor=2,并删除了Two这个元素,size也为2,
 
还记得是怎样判断while条件真假的吗?
 public boolean hasNext() {
return cursor != size;
}
所以当执行第三遍while语句时,cursor = size,hasnext返回false,结束循环,所以程序“来不及”报错就退出循环了,最后只输出一个元素。
 
解决办法
那么又如何在迭代集合时对集合进行操作呢?
方法一:
使用CopyOnWriteArrayList,而不是ArrayList
Collection num = new CopyOnWriteArrayList();
原理:
CopyOnWriteArrayList会先复制一个集合副本,当对集合进行修改时普遍的上修改的是副本里的值,修改完后再将原因集合的引用指向这个副本,避免抛出ConcurrentModificationException异常,使用了CopyOnWriteArrayList,多线程可以同时对这个容器进行迭代,而不会干扰或者与修改容器的线程相互干扰
底层源码实现如下:
 public boolean add(E e) {
final ReentrantLock lock = this.lock; //重入锁
lock.lock(); //加锁,效果与synchronized一样
try {
Object[] elements = getArray();
int len = elements.length; //得到数组长度
Object[] newElements = Arrays.copyOf(elements, len + 1); //复制元素到一个新数组里
newElements[len] = e; //原数组指向新数组
setArray(newElements);
return true;
} finally {
lock.unlock(); //在finally里解锁是为了避免发生死锁
}
}
方法二:
使用iterator对象的remove方法
if("Two".equals(element)){
iterator.remove(); //修改了此行
}else {
System.out.println(element);
}

 

集合操作出现的ConcurrentModificationException(源码分析)的更多相关文章

  1. 【集合框架】JDK1.8源码分析之HashMap(一) 转载

    [集合框架]JDK1.8源码分析之HashMap(一)   一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...

  2. 【集合框架】JDK1.8源码分析之ArrayList详解(一)

    [集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...

  3. 【集合框架】JDK1.8源码分析之HashMap & LinkedHashMap迭代器(三)

    一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继 ...

  4. 【集合框架】JDK1.8源码分析之ArrayList(六)

    一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...

  5. 【集合框架】JDK1.8源码分析之LinkedList(七)

    一.前言 在分析了ArrayList了之后,紧接着必须要分析它的同胞兄弟:LinkedList,LinkedList与ArrayList在底层的实现上有所不同,其实,只要我们有数据结构的基础,在分析源 ...

  6. 【集合框架】JDK1.8源码分析之Comparable && Comparator(九)

    一.前言 在Java集合框架里面,各种集合的操作很大程度上都离不开Comparable和Comparator,虽然它们与集合没有显示的关系,但是它们只有在集合里面的时候才能发挥最大的威力.下面是开始我 ...

  7. 【集合框架】JDK1.8源码分析之Collections && Arrays(十)

    一.前言 整个集合框架的常用类我们已经分析完成了,但是还有两个工具类我们还没有进行分析.可以说,这两个工具类对于我们操作集合时相当有用,下面进行分析. 二.Collections源码分析 2.1 类的 ...

  8. Java集合框架之接口Collection源码分析

    本文我们主要学习Java集合框架的根接口Collection,通过本文我们可以进一步了解Collection的属性及提供的方法.在介绍Collection接口之前我们不得不先学习一下Iterable, ...

  9. java集合【13】——— Stack源码分析走一波

    前言 集合源码分析系列:Java集合源码分析 前面已经把Vector,ArrayList,LinkedList分析完了,本来是想开始Map这一块,但是看了下面这个接口设计框架图:整个接口框架关系如下( ...

  10. 【集合框架】JDK1.8源码分析之HashMap(一)

    一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也 ...

随机推荐

  1. LDAP协议介绍

    LDAP协议基础概念  1. 从用途上阐述LDAP,它是一个存储静态相关信息的服务,适合"一次记录多次读取".经常使用LDAP服务存储的信息: 公司的物理设备信息(如打印机,它的I ...

  2. [置顶] 【VB.NET2010】注册表写入方法

    这里,以添加启动项为例: Imports Microsoft.Win32 Imports System.Reflection.Assembly   Private Sub SetAuto_Click( ...

  3. 菜鸟学EJB(二)——在同一个SessionBean中使用@Remote和@Local

    不废话.直接进入正题: 在Jboss4及曾经的版本号中,例如以下代码能够成功部署: package com.tjb.ejb; import javax.ejb.Local; import javax. ...

  4. Java 编程下使用 Class.forName() 加载类

    在一些应用中,无法事先知道使用者将加载什么类,而必须让使用者指定类名称以加载类,可以使用 Class 的静态 forName() 方法实现动态加载类.下面的范例让你可以指定类名称来获得类的相关信息. ...

  5. docke 网络配置

    一,docker 的bridge模式是和vmware中的nat模式类似的,但是如果想要弄成和vmwae中的bridge怎么办呢? 说明,bridge模式获取的Ip是与宿主机的ip是出于同一个网段的. ...

  6. Linux的用户和组

    1. 查看配置文件/etc/shadow第一行中root账号的第三个字段(以':'分隔)中的数字,请算一下这个数字是怎么来的?距离1970年1月1日到上次更改密码的时间的间隔天数.例如root密码日期 ...

  7. sql获取第n条数据

    select * from (select top n * from students) aa where not exists(select * from (select top n-1 * fro ...

  8. angularjs:[1] ui-router 权限控制

    1:简单的登录检查 $stateProvider .state('index', { url: "/", templateUrl: 'views/forwards/intro.ht ...

  9. mysql中自动更新时间CURRENT_TIMESTAMP

    timestamp的两个属性:CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP http://blog.163.com/qiongling007@126/b ...

  10. R语言数据处理

    写在前面:数据处理是数据分析与挖掘必不可少的步骤.下面列出一些常用的数据处理操作. 一.类型转换 用class()查看数据的类型,用as.类型名()进行类型转换. > num <- as. ...