作者 | 七十一

来源 | 程序员巴士

前言

什么是快速失败:fail-fast 机制是java集合(Collection)中的一种错误机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

运行如下代码,即可出现异常:

// 关于fail-fast的一些思考
public class FailFastTest {public static void main(String[] args) {// 构建ArrayListList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);for (int i : list) {list.remove(1);}}
}

控制台会输出如下异常:

为什么要报这个错?途中出错的地方是ArrayList中的代码,定位到该处代码:

final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}

modCount是这个集合修改的次数,这个属性来自AbstractList,而我们的ArrayList是继承了该抽象类的。

protected transient int modCount = 0;

expectedModCount又是啥呢?当我们进行遍历时候debug一下发现进行forEach循环的时候其实走了下面这个方法iterator,而且遍历这个底层还是走的hasNext方法

public Iterator<E> iterator() {return new Itr();}

判断是否有下一个元素

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

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];}

点进这个new Itr(),惊喜的发现原来这个expectedModCount是在这里被赋值的而且和modCount一样

private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount; // 注意:此处进行赋值............

接下来看下ArrayList的remove()方法,其对modCount进行了增加,这是导致报错的原因

public E remove(int index) {rangeCheck(index);modCount++; // 对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 workreturn oldValue;}

上面的next()方法这有调用一个checkForComodification()方法,下面贴一下这方法的代码

final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}

ArrayList里面remove()方法进行了modCount++操作,原来是我们对集合进行操作后改变了modCount导致上面代码成立,从而抛出异常

但是当我们使用Itr类的remove,也就是如下代码进行对元素改动时,不会抛出ConcurrentModificationException异常

public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;// 将ArrayList的modCount赋值给Itr类的expectedModCount //这样再次调用next方法时就不会出现这俩个值不一致 从而避免报错expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}

与ArrayList的remove()方法不同的是,该remove()方法调用ArrayList.this.remove(lastRet);后显然modCount++了,但是马上又让expectedModCount = modCount就是这样才不会抛出异常。

梳理整个流程:

1、for循环遍历实质上调用的是Itr类的方法进行遍历(Itr类实现了Iterator)

2、Itr类在构造的时候会将ArrayList的modCount(实际上modCount是AbstractList的属性,但是ArrayList继承了AbstractList)赋值给Itr类的expectedModCount

3、for循环中调用的remove()方法时ArrayList的,这个方法会对modCount进行++操作

4、remove方法调用后,继续遍历会调用Itr的next()方法,而这个next()方法中的checkForComodification()方法会对modCount和expectedModCount进行对比,由于remove方法已经操作过modCount因此这俩个值不会相等,故报错。

如何改进?

1、可以使用Itr中的remove方法进行改进,改进代码如下

public static void main(String[] args) {// 构建ArrayListList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()) {iterator.next();iterator.remove();}System.out.println(list.size()); // 0}

2、使用CopyOnWriterArrayList来代替Arraylist,它对ArrayList的操作时会先复制一份数据出来操作完了再将其更新回去替换掉旧的,所以CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。这是采用了CopyOnWriterArrayList的fail-safe机制,当集合的结构被改变的时候,fail-safe机制会在复制原集合的一份数据出来,然后在复制的那份数据遍历,fail-safe机制,在JUC包的集合都是有这种机制实现的。

虽然fail-safe不会抛出异常,但存在以下缺点

1、复制时需要额外的空间和时间上的开销。

2、不能保证遍历的是最新内容。

总结

对于fail-fast机制,我们要操作List集合时可以使用Iterator的remove()方法在遍历过程中删除元素,或者使用fail-safe机制的CopyOnWriterArrayList,当然使用的时候需要权衡下利弊,结合相关业务场景。

往期推荐

低代码发展专访系列之八:低代码平台能够打破企业「应用孤岛」现象吗?

Medusa又一个开源的替代品

用了HTTPS,没想到还是被监控了

快速搭建实验环境:使用 Terraform 部署 Proxmox 虚拟机

点分享

点收藏

点点赞

点在看

为什么简单的删除集合中的元素竟然报错了?相关推荐

  1. PageCollectionView[Bug],使用Filter的时候,删除集合中的元素,会导致ArgumentOutOfRangeException...

    PageCollectionView,没有使用Filter的时候一切正常:当使用Filter的时候,删除集合中的元素,会抛出如下异常: 1: 指定的参数已超出有效值的范围.\n参数名: index 2 ...

  2. 循环的时候去删除集合中的元素 java.util.ConcurrentModificationException

    使用for循环,删除集合中的元素,会报错 java.util.ConcurrentModificationException 只能通过迭代器 iterator删除 1:在while循环中使用itera ...

  3. java删除集合元素吗_java如何删除集合中的元素

    java如何删除集合中的元素 如何使用java删除集合中的'元素呢?下面是小编给大家提供的删除集合中元素的常见方法,欢迎阅读,更多详情请关注应届毕业生考试网. Java代码如下: package co ...

  4. vue中如何在方法中动态的删除集合中的元素!?

    参考https://blog.csdn.net/weixin_42230550/article/details/87990486 vue中,如何在方法中动态的删除集合中的元素? me.btn_elem ...

  5. 删除集合中特定元素的几种情况

    从集合中删除元素一直是一个比较容易遗漏的知识点,今天来给大家介绍一下删除集合​​中特定元素的一些情况. ​(一)List如何实现遍历删除 以ArrayList为例 List<String> ...

  6. Java中删除集合中的指定元素

    引出问题 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题. 哪些遍历存在问题? a.迭代器遍历集合且直接用集合删除元素的时候可能出现. b.增强for循环遍历集合且直接用集合删除元 ...

  7. linq判断集合中相同元素个数_JavaSe集合的概念以及集合框架介绍

    ###集合 今天任务 1.概念1.1 集合的概念1.2 集合的框架结果介绍1.3 集合和数组的对比 2.Collection接口2.1 Collections中常用的方法 3.泛型3.1 什么是泛型3 ...

  8. Java循环删除集合多个元素的正确打开方式

    首先说下不正确的打开方式: 第一:使用for循环删除集合的元素,示例代码如下 1 ArrayList<String> list = new ArrayList<String>( ...

  9. 如何删除JAVA集合中的元素

    经常我们要删除集合中的某些元素.有些可能会这么写. public void operate(List list){ for (Iterator it = list.iterator(); it.has ...

最新文章

  1. 数据可视化必修课 - 图表篇
  2. 简单的节流函数throttle
  3. 约束布局constraint-layout导入失败的解决方案 - 转
  4. Intel 64/x86_64/IA-32/x86处理器 - SIMD指令集 - MMX技术(7) - 状态清除指令 小结
  5. IDEA上传本地项目到SVN
  6. ArrayList概述
  7. Spring动态的切换数据源
  8. setNavigationBarTitle小程序基础性操作标题改变
  9. Cadence全家桶Capture+Allegro流程-5-编辑焊盘并制作封装
  10. 数据挖掘与可视化相关论文
  11. 最新Axure激活码
  12. C语言找出1000之内的完数
  13. linux中#和## 用法
  14. VGG多种网络结构的搭建以及感受野的计算
  15. python跳转下一页_我怎么能跳转到下一页呢
  16. 业务复习-微信登录/第三方登录实现
  17. “北大数学系扫地僧” 等十人获奖,均分1000万元,达摩院2021青橙奖出炉
  18. 好工具推荐系列:Windows系统查看各个进程/网速/CPU的软件(查看系统资源工具)
  19. 依靠云计算推动企业业务模式变革
  20. mysql 统计每年的数据统计_Mysql统计每年每个月的数据——详细教程

热门文章

  1. 【纠错记录】本地FTP服务器无法被外部连接
  2. python读取word指定内容_python读取word 中指定位置的表格及表格数据
  3. 内存条能4+8混插吗?_笔记本内存条双通道提升有多大?实测FORESEE,你知道好处在哪吗...
  4. directshow 旋转_宜昌中心加工机+A:B型号,高速旋转接头加工
  5. hibernate mysql autocommit_Hibernate4 中为什么我没有用commit()方法直接用save就存到数据库了?...
  6. mysql odbc.ini_关于unixodbc中odbc.ini和odbcinst.ini的介绍
  7. 只引入部分elementui_腾讯动漫确定引入假面骑士亚极陀和甲斗王 四仔:是不是玩不起...
  8. ue4玻璃材质_UE4-材质
  9. 多所高校通知,新学期延期开学!做好线上教学准备
  10. 2020,这些前沿技术成全球关注热点