Exception in thread "main" java.util.ConcurrentModificationException

本贴最后更新于 2363 天前,其中的信息可能已经时移世改

遍历 List 是我们在写代码中经常碰到的,有时候我们会碰到想在遍历的途中删掉某些元素的需求,但一不小心可能就会报快速失败错误(FastFail),这其实跟 List 当中的一些实现有关,下面我选取了两种情况,来自知乎大神的回答,加上自己的疑惑和总结。

作者:RednaxelaFX

链接:https://www.zhihu.com/question/56586732/answer/149650876

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总结下就是两种情况:一种是可以删除末尾的元素,另一种是在倒数第二时可以删除任何一个元素。

我觉得题主的首要问题是被自己写的代码(以及 JDK 的 ArrayList 那可恶的 API)给坑了。

ArrayList 上有两个版本的 remove 方法:

public E remove(int index)

public boolean remove(Object o)

题主很可能以为自己调用的是第二个版本,但实际上调用的是第一个版本——remove(3) 删除了位于末尾的元素,而不是位于倒数第二的元素。

ArrayList.iterator() 返回出来的 Iterator,里面的 hasNext()是不关心 modification count 的,而 next()会去检查 modification count:

List 中的内部类

/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") 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]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

所以题主的那个程序实际做的事情就是:

  • 通过 ArrayList.iterator() 得到了一个新的 iterator,开始遍历

  • list.remove(3):删除了位于 index 3 的元素(Integer.valueOf(4) 得到的对象)

  • 然后调用 iterator.hasNext(),得到 false,于是就退出了循环而没有去执行那个会检查 modification count 的 next()方法。

ArrayList 的 JavaDoc 说:

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

没有特别指定 Iterator 里的哪些方法一定会根据 fail-fast 原则而抛异常。但 Iterator.hasNext()的 JavaDoc 说:

hasNext

boolean hasNext()

Returns true if the iteration has more elements. (In other words, returns true if next() would return an element rather than throwing an exception.)

Returns:true if the iteration has more elements

就这个规定来说,我觉得题主观察到的现象应该算是 JDK 实现的上的巧合:因为如果在这个位置调用 next()的话会抛 ConcurrentModificationException 异常,所以 hasNext()也要返回 false,于是就好啦。

但 JDK 这个具体实现看起来还是有 bug,应该让 hasNext()也做 checkForComodification()的不抛异常对应动作才对。

就这样。

还是未能解决下面的疑问:

在判断的是倒数第二个的时候,所有的元素都能删。

List list = new ArrayList(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); list.add("f"); Iterator it = list.iterator(); while (it.hasNext()) { String itt = (String) it.next(); if (itt.equals("e")) { list.remove("a"); } } for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }

下面是解释:

作者:xRay

链接:https://www.zhihu.com/question/56916067/answer/151995061

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

为什么没抛异常,跟下代码其实就一清二楚了。

ArralyList 有个版本号 modCount,每次修改 ArrayList 时版本号就会往上加。Iterator 里面也有个版本号 expectedModCount 它的初始值就是 modCount。只有 expectedModCount != modCount 才会抛异常。所以 printList 里面的绝对不会抛异常。那问题就出在进行 remove 的那个迭代器上,首先迭代器是在什么时候比较这两个值呢? 答案是在 remove 跟 next 的时候,也就是说 hasNext 它不会抛出这个异常。

List 中的删除方法:

/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ 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; }

然后,看下题主的这段代码

Iterator iterator = list.iterator(); while(iterator.hasNext()) { Integer integer = iterator.next(); if(integer == 2) list.remove(integer); }

这里面调用了 List 的 remove 方法,所以它不会抛出 ConcurrentModificationException。问题是 remove 之后为什么 hasNext 会返回 false。我们看下 hasNext 方法

int cursor; // index of next element to return

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

看注释,cursor 是指向一个元素的,也就是当 list.remove(integer=2)的时候,cursor 实际等于 2。而 list.remove(integer)后 size=3-1=2,所以 hasNext 返回 false,也就不会执行 next 方法,所以也就不会抛出 ConcurrentModificationException。

不过,这个问题,更有意思的 ArrayList 有两个 remove 方法

public E remove(int index) boolean remove(Object o)

当参数是 Integer 时会调用哪个? 我在 JLS 中找到这么一段,意思是选择重载函数时不会优先考虑装箱跟拆箱

The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8217 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • visus

    同步写同步读,试一试用 COW 模式

推荐标签 标签

  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 62 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 546 关注
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖 • 6 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 3 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    20 引用 • 37 回帖 • 572 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    108 引用 • 295 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 28 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 1 关注
  • 反馈

    Communication channel for makers and users.

    120 引用 • 906 回帖 • 280 关注
  • 叶归
    12 引用 • 56 回帖 • 23 关注
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    4 引用 • 7 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 29 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 643 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖 • 2 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 2 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    61 引用 • 29 回帖 • 12 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    83 引用 • 412 回帖
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 564 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 442 关注
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 313 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 7 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 37 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 540 回帖
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖 • 1 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    108 引用 • 153 回帖 • 1 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 699 关注