集合的操作出现的ConcurrentModificationException(源码分析)

摘要:

为了保证线程安全,在迭代器迭代的过程中,线程是不能对集合本身进行操作(修改,删除,增加)的,否则会抛出ConcurrentModificationException的异常。 

示例:

 1  public static void main(String[] args) {
 2         Collection num = new ArrayList<String>();
 3         num.add("One");
 4         num.add("Two");
 5         num.add("Three");
 6
 7         Iterator<String> iterator= num.iterator();
 8         while(iterator.hasNext()){
 9             String element = iterator.next();
10             if("One".equals(element)){
11                iterator.remove();
12             }else {
13                 System.out.println(element);
14             }
15         }
16     }
17 }

运行结果:

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具体实现如下:

 1  public E next() {
 2             checkForComodification();
 3             int i = cursor;
 4             if (i >= size)
 5                 throw new NoSuchElementException();
 6             Object[] elementData = ArrayList.this.elementData;
 7             if (i >= elementData.length)
 8                 throw new ConcurrentModificationException();
 9             cursor = i + 1;
10             return (E) elementData[lastRet = i];
11   }

接着代码进入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条件真假的吗?

1 public boolean hasNext() {
2 return cursor != size;
3 }

所以当执行第三遍while语句时,cursor = size,hasnext返回false,结束循环,所以程序“来不及”报错就退出循环了,最后只输出一个元素。

解决办法

那么又如何在迭代集合时对集合进行操作呢?

方法一:

使用CopyOnWriteArrayList,而不是ArrayList

Collection num = new CopyOnWriteArrayList();

原理:

CopyOnWriteArrayList会先复制一个集合副本,当对集合进行修改时普遍的上修改的是副本里的值,修改完后再将原因集合的引用指向这个副本,避免抛出ConcurrentModificationException异常。

底层源码实现如下:

 1 public boolean add(E e) {
 2    final ReentrantLock lock = this.lock; //重入锁
 3    lock.lock(); //加锁,效果与synchronized一样
 4 try {
 5    Object[] elements = getArray();
 6    int len = elements.length; //得到数组长度
 7    Object[] newElements = Arrays.copyOf(elements, len + 1); //复制元素到一个新数组里
 8    newElements[len] = e; //原数组指向新数组
 9    setArray(newElements);
10    return true;
11 } finally {
12    lock.unlock(); //在finally里解锁是为了避免发生死锁
13 }
14 }

方法二:

使用iterator对象的remove方法

if("Two".equals(element)){
    iterator.remove(); //修改了此行
}else {
    System.out.println(element);
}

 

时间: 2024-10-21 03:25:16

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

Java 集合系列(四)—— ListIterator 源码分析

以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比   Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素.  Iterator 包含四个方法,分别是:next().hasNext().remove().forEachRemaining(Consumer<? super E> action)   Collection 接口继承 java.lang.Iterable,因此所有 Collection 实现类都拥有 Iterator 迭代能力

7.Java集合-Arrays类实现原理及源码分析

Java集合---Arrays类源码解析 转自:http://www.cnblogs.com/ITtangtang/p/3948765.html 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对

java集合框架02——Collection架构与源码分析

Collection是一个接口,它主要的两个分支是List和Set.如下图所示: List和Set都是接口,它们继承与Collection.List是有序的队列,可以用重复的元素:而Set是数学概念中的集合,不能有重复的元素.List和Set都有它们各自的实现类. 为了方便,我们抽象出AbstractCollection类来让其他类继承,该类实现类Collection中的绝大部分方法.AbstractList和AbstractSet都继承与AbstractCollection,具体的List实现

java集合框架07——Map架构与源码分析

前几节我们对Collection以及Collection中的List部分进行了分析,Collection中还有个Set,由于Set是基于Map实现的,所以这里我们先分析Map,后面章节再继续学习Set.首先我们看下Map架构图: 从图中可以看出: 1. Map是一个接口,Map中存储的内容是键值对(key-value). 2. 为了方便,我们抽象出AbstractMap类来让其他类继承,该类实现了Map中的大部分API,其他Map的具体实现就可以通过直接继承AbatractMap类即可. 3.

Java集合基于JDK1.8的ArrayList源码分析

本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集合类更好一些,这是使用数组的一大优势.但是我们知道数组存在致命的缺陷,就是在初始化时必须指定数组大小,并且在后续操作中不能再更改数组的大小.在实际情况中我们遇到更多的是一开始并不知道要存放多少元素,而是希望容器能够自动的扩展它自身的容量以便能够存放更多的元素.ArrayList就能够很好的满足这样的

NHibernate源码分析

NHibernate源码分析之开篇: 计划和安排 只从使用NHibernate以来,请被其强大的功能和使用的简洁所吸引. 为了进一步研究NHibernate,决定分析其源代码,如有感兴趣者,欢迎一起研究. 这里列出了将要分析的部分: 1.        NHibernate配置和持久对象映射文件 2.        NHibernate架构分析(uml图) 3.        NHibernate源码分析之一: 配置信息 4.        NHibernate源码分析之一(续): 对象映射 5

spring5源码分析系列(五)——IOC容器的初始化(三)

前言:上一篇讲到了DocumentLoader将Bean定义资源转换为Document对象,此篇我们继续后面的内容. (9)XmlBeanDefinitionReader解析载入的Bean定义资源文件 XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象, 接下来调用registerBeanDefinitions启动Spring IOC容

jquery2源码分析系列目录

学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中可以学到很多w3c新的标准( 如html5,css3,ECMAScript).原文地址是:http://www.cnblogs.com/aaronjs/p/3279314.html 关于1.x.x版的jquery源码分析系列,本博客也转载了一个地址http://www.cnblogs.com/jav

jQuery-1.9.1源码分析系列完毕目录整理

jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二)jQuery选择器 jQuery-1.9.1源码分析系列(二)jQuery选择器续1 jQuery-1.9.1源码分析系列(二)jQuery选择器续2——筛选 jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——词法解析 jQuery-1.9.1源码分析系列(三) Sizzle选择

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(