Exception in thread "main" java.util.ConcurrentModificationException

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

遍历 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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3167 引用 • 8207 回帖

相关帖子

欢迎来到这里!

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

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

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

推荐标签 标签

  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 1 关注
  • 音乐

    你听到信仰的声音了么?

    59 引用 • 509 回帖
  • 996
    13 引用 • 200 回帖 • 1 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 227 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 2 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    7 引用 • 26 回帖 • 3 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 293 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 1 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 427 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖 • 2 关注
  • CodeMirror
    1 引用 • 2 回帖 • 114 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 547 关注
  • 反馈

    Communication channel for makers and users.

    123 引用 • 906 回帖 • 191 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 628 关注
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 495 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    19 引用 • 23 回帖 • 682 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    51 引用 • 37 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 7 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 621 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 553 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 111 关注
  • Sillot

    Sillot (汐洛)孵化自思源笔记,致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点
    Github 地址:https://github.com/Hi-Windom/Sillot

    14 引用 • 4 回帖 • 26 关注
  • 自由行
    1 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 9 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    207 引用 • 2031 回帖