ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。
一:什么时候出现?
当我们用迭代器循环list的时候,在其中用list的方法新增/删除元素,就会出现这个错误。
package com.sinitek.aml; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class ConcurrentModificationExceptionDemo { public static void main(String[] args) { List<String> arrayList = new ArrayList<>(); arrayList.add("a");
arrayList.add("b");
arrayList.add("c"); Iterator<String> iterator = arrayList.iterator();
while(iterator.hasNext()){
String tmp = iterator.next();
if ("a".equals(tmp)){
arrayList.remove(tmp); }
} }
}
二:为什么会出这个错误?
如上图所示,运行代码就会有这个错误,我们看一下堆栈。
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.sinitek.aml.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:22)
可以看到,是在ArrayList里面的Itr内部类里的checkForModification()方法里,抛出的这个异常,我们看下源码。
final void checkForComodification() {
//如果修改的次数 和 预期修改的次数不一致,就会报错
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
可以看到,上面两个关键的值是:modCount和expectModCount,其中的modCount来自ArrayList的父类,AbstractList,有兴趣的可以看下上面的翻译,也说明了这个字段的作用,正是为了fail-fast(快速失败)设计的。
该值会在new出对象的时候,初始化为0;并且在每次的修改/删除操作时自增。
//arrayList的add方法,会先调用一次检查容量是否够的操作,我们可以看到,jdk专门在里面加了注释,会 Increments modCount, 增加modCount的值
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} //确保新增元素容量大小够用的方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //首先先自增一次 // overflow-conscious code
if (minCapacity - elementData.length > 0) //判断最小需要的容量是否大于数组的长度(这里有个疑问,为什么一定相减然后判断是否>0,直接比较会有溢出的问题需要考虑么)
grow(minCapacity); //扩容
} //删除方法,也会有modCount++的操作
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;
}
那我们看一下,expectedModCount时怎么变化的。
在ArrayList的内部类Itr(这个命名是不是有点随意了)里面,会看到expectedModCount是作为成员变量一起初始化的,所以我们在最开始的时候,调用list.iterator()方法的时候,就会初始化expectedModCount,将他变成和modCount一样的值。所以问题出在:
1. new ArrayList,新增后,modCount会有一个初始值0。
2. 调用iterator()方法后,会把expectedModCount的值初始化为modCount的值,此时,这两个值是一致的。
3. 如果在循环里,使用list对象新增/删除对象(本文最上面所作的操作),modCount就会自增变化,而此时,expectedModCount却不会改变。
4. 调用iterator里面的next() -> checkForComodification(),判断modCount==expectModCount的时候,就会发现不一致,从而抛出这个异常。
三:怎么解决。
有两种方法。
- 使用Itr类中的remove方法移除,但是如果用的是从Iterable继承的iterator返回的Iterator是没有add()方法的接口的,如果需要add(),那么需要调用list专有的迭代器listInterator()方法返回的ListIterator().
- 不适用迭代器,使用原生的循环方式,这样就可以避免这个异常。(增强for只是一个语法糖(Syntactic Sugar)本质上还是使用了迭代器,也会有这个异常)。
四:为什么要定义这个异常?
凡事多问一个为什么,jdk为什么要怎么设计,很简单,其实从命名上就能看出来,当我们在循环的时候,无论是我们在迭代循环的时候(目前我们就是这种情况),或者是其他的线程修改了这个(这个应该是jdk主要想解决的),都会出现数据不一致的情况,所以需要这个异常,让你fail-fast,早点异常早点再次尝试等。
Q:为什么不在add()方法的时候,同时修改expectModCount,这样就他们一致了不就行了?
A:add()方法在调用的时候,有可能没有这个Itr这个对象,而且单单修改值是没有用的,需要把对应的迭代器内部保存数据的值也对应的修改,只修改expectModCount有点类似掩耳盗铃。
Q:那jdk可以新建一个重载方法,让itr对象当作参数,把他传进来,修改对应的保存数据的数组和对应的expectModCount,这样就能保证两个都一致了把。
A:确实,这种方法对于上文这种fail-fast确实适用,但是还有一种情况就是:可能修改list的线程和迭代的线程都不是同一个,所以根本就没有itr这个对象,也就无法修改这个值了。
ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。的更多相关文章
- ConcurrentModificationException并发修改异常
//创建集合对象 Collection c = new ArrayList(); c.add("hello"); c.add("world"); c.add(& ...
- 集合并发修改异常-foreach的时候不可修改值
直接上代码: 无意间发现的://这个方法本身是为后面的集合去掉前面集合的重复数据一直报错,并发修改异常,仔细看mainList正在迭代循环,然后我进行了remove操作,这个时候就会报这个错.故:总结 ...
- 29.2 Iterator 迭代器ConcurrentModificationException:并发修改异常处理
/** Iterator:迭代器* * 需求:判断集合中是否包含元素java,如果有则添加元素android * Exception in thread "main" java.u ...
- 【Java笔记】以并发修改异常为例总结的出错解决办法
先来看出错代码: /*需求: 遍历已有集合 如果在集合中发现存在字符串元素"world" 则在"world"后添加元素"javaee" */ ...
- List集合遍历时修改元素出现并发修改异常总结
什么是并发修改异常: 当我们在遍历实现了collection接口与iterator接口的集合时(List.Set.Map), 我们可以通过遍历索引也可以通过迭代器进行遍历.在我们使用迭代器进行遍历集合 ...
- 集合框架之——迭代器并发修改异常ConcurrentModificationException
问题: 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. 使用普通迭代器出现的异常: ...
- ArrayList在foreach删除倒数第二个元素不抛并发修改异常的问题
平时我们使用ArrayList比较多,但是我们是否知道ArrayList在进行foreach的时候不能直接通过list的add或者move方法进行删除呢, 原因就是在我们进行foreach遍历的时候, ...
- ConcurrentModificationException(并发修改异常)的解决
[异常解释] ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.[产生的原因] 迭代器是依赖于集合而存在的,在判断成功后,集合 ...
- 理解和解决Java并发修改异常ConcurrentModificationException(转载)
原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...
- 大杂烩 -- Iterator 并发修改异常ConcurrentModificationException
基础大杂烩 -- 目录 大杂烩 -- Java中Iterator的fast-fail分析 大杂烩 -- Iterator 和 Iterable 区别和联系 问题: 在集合中,判断里面有没有" ...
随机推荐
- torch.meshgrid
1:https://blog.csdn.net/weixin_39504171/article/details/106356977 2: https://pytorch.org/docs/stable ...
- python 作用域与命名空间
作用域 作用域分为: 全局作用域 局部作用域 在函数内部的作用域叫做局部作用域,局部作用域中的变量叫做局部变量 非函数内部的作用域叫做全局作用域,全局作用域中的变量叫做全局变量 局部作用域可以使用全局 ...
- X-Pack:创建阈值检查警报
简单的事情应该简单(Simple things should be simple),这是Elastic {ON} '17的主题之一,Elastics收到了许多关于使用简单易用的UI创建警报的请求.事实 ...
- Ceph 有关知识简介
Ceph 存储集群至少需要一个 Ceph Monitor 和两个 OSD 守护进程.而运行 Ceph 文件系统客户端时,则必须要有元数据服务器( Metadata Server ). Ceph OSD ...
- Pod的滚动升级过程
- 微信小程序第三方授权登录
登录流程时序图: 1.调用uni.getProvider()获取服务供应商,参数service确定是选择对应的什么操作,此处选择授权登录oauth 代码如下: 2.调用登录接口uni.login(), ...
- 关于成本标签管理-基于-Resource Groups & Tag Editor-统计指定Project-所有资源
背景:因我们所有AWS都是使用Project标签作为成本标签的,今天因一个项目决定彻底退役下线 于是决定要完全清理此项目的所有资源,防止继续产生费用~ 首先想到的去通过Project 在ec2 , s ...
- python之流程控制上-if、while
流程控制 编写程序,是将自己的逻辑思想记录下来,使得计算机能够执行的过程. 而流程控制,则是逻辑结构中十分重要的一环. 在程序中,基础的流程结构分为顺序结构.分支结构.顺序结构 顺序结构自不必多说,上 ...
- day07-2MySQL索引
MySQL索引 说起提高数据库性能,索引是最物美价廉的东西了.不用加内存,不用改程序,不用调sql,查询速度就能提高千百倍. 例子 首先,创建一个有800万条数据的表 -- 创建测试数据库 tmp C ...
- 会话跟踪技术 - Cookie 和 Session 快速上手 + 登陆注册案例
目录 1. 会话跟踪技术概述 2. Cookie 2.1 Cookie的概念和工作流程 2.2 Cookie的基本使用 2.3 Cookie的原理分析 2.4 Cookie的使用细节 2.4.1 Co ...