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

53ed4819c5d6f4f4c3abcec044c048e3.gif

作者 | 七十一

来源 | 程序员巴士

前言

什么是快速失败: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);}}
}

控制台会输出如下异常:

e5a6e4e22386cb06c44138df1cb9b3f1.png

为什么要报这个错?途中出错的地方是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,当然使用的时候需要权衡下利弊,结合相关业务场景。

00dd0777fdd76c4da92fc4e8536a95ff.gif

0988cd713236489c716eea300c74cb16.png

往期推荐

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

Medusa又一个开源的替代品

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

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

8d574e240e1efa37b51f37df01690be1.gif

点分享

7988eed406b06c51425819b552eb217e.gif

点收藏

85f4de573c4afb44b7ea22b06bf23854.gif

点点赞

e4ad9759f8b9085438bef37c892b1c0c.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文详解Redis中BigKey、HotKey的发现与处理

简介&#xff1a; 在Redis的使用过程中&#xff0c;我们经常会遇到BigKey&#xff08;下文将其称为“大key”&#xff09;及HotKey&#xff08;下文将其称为“热key”&#xff09;。大Key与热Key如果未能及时发现并进行处理&#xff0c;很可能会使服务性能下降、用户体验变差&a…

阿里云CDN操控2.0版本正式发布

简介&#xff1a; 2021年8月&#xff0c;阿里云边缘云CDN完成过去3年来最大的一次版本升级。 2021年8月&#xff0c;阿里云边缘云CDN完成过去3年来最大的一次版本升级。本次升级根据上万企业客户的使用反馈和行业应用特征&#xff0c;从简单开通到个性化定制&#xff0c;从内容…

向xxxhub发了一个数据包,发现了···

作者 | 轩辕之风来源 | 编程技术宇宙那天&#xff0c;我突然想到一个问题&#xff1a;当我访问那个让万千宅男程序员为之着迷的GitHub时&#xff0c;我电脑发出的数据包是如何抵达大洋彼岸的GitHub服务器的呢&#xff0c;这中间又要经过哪些节点呢&#xff1f;让我们一起来探究…

使用 Flink Hudi 构建流式数据湖

简介&#xff1a; 本文介绍了 Flink Hudi 通过流计算对原有基于 mini-batch 的增量计算模型的不断优化演进。 本文介绍了 Flink Hudi 通过流计算对原有基于 mini-batch 的增量计算模型不断优化演进。用户可以通过 Flink SQL 将 CDC 数据实时写入 Hudi 存储&#xff0c;且在即将…

android获取版本号报错,Android开发:获取安卓App版本号的方法步骤

在Android开发过程中&#xff0c;想要开发一个完整功能的App&#xff0c;各个地方的内容都要涉及到&#xff0c;比如获取App的系统版本号就是必须要有的功能。Android的App版本号相关内容比iOS的App版本号内容要多&#xff0c;而且iOS版的App版本信息跟Android的还不一样。本篇…

运营也用的起来的数据分析工具:Quick BI即席分析详解

简介&#xff1a; 数据部门是一个容易被投诉的“高危”部门&#xff0c;需求响应慢、数据准确性不高会影响业务的发展。 然而数据分析师每周动辄就有几十个需求在手&#xff0c;无限的加班也无法解决所有问题&#xff0c;到底怎样才能改变BI分析师的需求响应问题呢&#xff1f;…

【产品动态】解读Dataphin流批一体的实时研发

简介&#xff1a; Dataphin作为一款企业级智能数据构建与管理产品&#xff0c;具备全链路实时研发能力&#xff0c;从2019年开始就支撑可集团天猫双11的实时计算需求&#xff0c;文章将详细介绍Dataphin实时计算的能力。 背景 每当双11全球购物狂欢节钟声响起&#xff0c;上千…

Aruba与中国电信国际有限公司达成战略合作 助力中国企业扬帆出海

2022年1月12日&#xff0c;慧与科技公司 (NYSE: HPE) 旗下Aruba日前宣布&#xff0c;与中国电信国际有限公司&#xff08;CTG&#xff09;签署MSP&#xff08;托管服务运营商&#xff09;战略合作伙伴协议&#xff0c;Aruba的产品将纳入中国电信国际有限公司的主营产品线。协议…

模仿Spring实现一个类管理容器

简介&#xff1a; 项目的初衷是独立作出一个成熟的有特色的IOC容器,但由于过程参考Spring太多,而且也无法作出太多改进,于是目的变为以此项目作为理解Spring的一个跳板,与网上的一些模仿Spring的框架不同,本项目主要是针对注解形式 概述 项目的初衷是独立作出一个成熟的有特色…

湖仓一体化的路,很多人都只走了一半

2022已至&#xff0c;如果回看2021&#xff0c;这一年无疑是数据的价值进一步体现的一年。数据应用场景不断丰富&#xff0c;从工业、交通、金融到制造&#xff0c;几乎无处不在。当然&#xff0c;数据价值的迅速提升也给开发者和相关企业带来了新的问题。数据量的爆发让存储成…

学术顶会再突破!计算平台MaxCompute论文入选国际顶会VLDB 2021

简介&#xff1a; VLDB 2021上&#xff0c;阿里云计算平台MaxCompute参与的论文入选&#xff0c;核心分布式调度执行引擎Fangorn、基于TVR Cost模型的通用增量计算优化器框架Tempura等分别被Industry Track、Research Track录取。 一、顶会概览 VLDB 2021上&#xff0c;阿里云…

技术干货 | 应用性能提升 70%,探究 mPaaS 全链路压测的实现原理和实施路径

简介&#xff1a; 全链路压测方案下&#xff0c;非加密场景下至少有 70% 的性能提升&#xff0c;加密场景下 10%的性能提升&#xff0c;并在 MGS 扩容完成后可实现大幅的性能提升&#xff0c;调优的结果远超预期。 业务背景 随着移动开发行业的步入存量时代&#xff0c;App 整…

投稿指南 | 云计算领域最前沿资讯、技术,期待您的专业解读!

我们是谁&#xff1f;CSDN云计算是CSDN旗下官方账号&#xff0c;提供云计算、大数据、虚拟化、数据中心、OpenStack、CloudStack、机器学习、智能算法等相关云计算观点、云计算技术、云计算平台、云计算实践、云计算产业咨询等服务。内容平台方面&#xff0c;我们的目标读者主要…

DataWorks 功能实践速览03期 — 生产开发环境隔离

简介&#xff1a; DataWorks功能实践系列&#xff0c;帮助您解析业务实现过程中的痛点&#xff0c;提高业务功能使用效率&#xff01; 往期回顾&#xff1a; DataWorks 功能实践速览01期——数据同步解决方案&#xff1a;为您介绍不同场景下可选的数据同步方案。DataWorks 功…

鸿蒙手表esim,鸿蒙手表终于来了!或将支持 eSIM,实现独立通话

原标题&#xff1a;鸿蒙手表终于来了&#xff01;或将支持 eSIM&#xff0c;实现独立通话根据此前的爆料消息&#xff0c;华为将于 6 月份带来与鸿蒙相关的产品发布会&#xff0c;备受瞩目的平板、手表等新品也将亮相。临近产品发布&#xff0c;华为官方也开始了新品的预热。今…

Pull or Push?监控系统如何选型

简介&#xff1a; 对于建设一套公司内部使用的监控系统平台&#xff0c;相对来说可选的方案还是非常多的&#xff0c;无论是用开源方案自建还是使用商业的SaaS化产品&#xff0c;都有比较多的可选项。但无论是开源方案还是商业的SaaS产品&#xff0c;真正实施起来都需要考虑如何…

k8s 集群居然可以图形化安装了?

作者 | 小碗汤来源 | 我的小碗汤今天分享一个可以图形化搭建k8s集群的项目&#xff0c;不妨试一试~本项目是基于 Kubespray 提供图形化的 K8S 集群离线安装、维护工具。Kubespray&#xff1a;https://github.com/kubernetes-sigs/kubesprayKuboard-SprayKuboard-Spray 是一款可…

poi excel导入 判断合并单元格_Excel合并单元格,你需要知道的那些事

合并单元格&#xff0c;是我们经常使用的一个功能。借助合并单元格功能&#xff0c;我们可以制作跨列表头&#xff0c;可以对数据进行显示上的分类&#xff0c;使数据看起来更加清晰明了&#xff0c;让我们的Excel表格看起来更加专业。找到菜单栏的合并单元格功能&#xff0c;我…

当设计模式遇上 Hooks

简介&#xff1a; 数据结构与设计模式能够指导我们在开发复杂系统中寻得一条清晰的道路&#xff0c;既然都说 Hooks 难以维护&#xff0c;那就尝试让「神」来拯救这混乱的局面。对于「设计模式是否有助于我们写出更优雅的 Hooks 」这个问题&#xff0c;看完本文&#xff0c;相信…

PostgreSQL数据目录深度揭秘

简介&#xff1a; PostgreSQL是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;被业界誉为“先进的开源数据库”&#xff0c;支持NoSQL数据类型&#xff0c;主要面向企业复杂查询SQL的OLTP业务场景&#xff0c;提供…