单线程调用 arraylist.remove() 会抛 concurrentmodificationexception,因为迭代器与集合共享 modcount,遍历时外部修改会破坏 fail-fast 契约;正确做法是用 iterator.remove()、removeif() 或倒序索引删除。

为什么单线程调用 ArrayList.remove() 也会抛 ConcurrentModificationException
因为迭代器(Iterator)和集合本体共享同一个 modCount 计数器,只要在迭代过程中有非迭代器自身的修改操作,就会触发校验失败。这不是并发问题,而是“结构修改未走迭代器通道”的契约破坏。
常见错误现象:for (String s : list) { if (s.isEmpty()) list.remove(s); } —— 看似单线程、顺序执行,但底层 Iterator.next() 每次都会检查 modCount 是否被外部改动过。
- 使用场景:遍历中根据条件删除元素(比如过滤空字符串、清理过期任务)
-
ArrayList、HashMap、LinkedList等所有基于modCount的集合都遵循这一机制 - 不是 bug,是 fail-fast 设计的主动报错,避免产生不可预测的跳过或重复遍历
安全删除的三种写法及适用边界
核心原则:让修改动作由迭代器自己完成,或彻底避开迭代器。
- 用
Iterator.remove()—— 最常用,适用于需要判断后立即删的场景Iterator<string> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.isEmpty()) it.remove(); }</string> - 用
removeIf()(Java 8+)—— 简洁,但内部仍用迭代器,不支持中断或额外副作用list.removeIf(String::isEmpty); - 倒序
for循环 + 索引删除 —— 绕过迭代器,适合需精确控制索引或兼容老版本的场景for (int i = list.size() - 1; i >= 0; i--) { if (list.get(i).isEmpty()) list.remove(i); }
注意:removeIf() 在 CopyOnWriteArrayList 中不会抛异常,但它是线程安全的代价:每次修改都复制数组,单线程下性能反而更差。
酷纬企业网站管理系统Kuwebs是酷纬信息开发的为企业网站提供解决方案而开发的营销型网站系统。在线留言模块、常见问题模块、友情链接模块。前台采用DIV+CSS,遵循SEO标准。 1.支持中文、英文两种版本,后台可以在不同的环境下编辑中英文。 3.程序和界面分离,提供通用的PHP标准语法字段供前台调用,可以为不同的页面设置不同的风格。 5.支持google地图生成、自定义标题、自定义关键词、自定义描
立即学习“Java免费学习笔记(深入)”;
ConcurrentModificationException 不等于多线程竞争
这个异常名极具误导性。它只表示“检测到结构修改与当前迭代状态不一致”,和线程数无关。
- 即使整个方法加了
synchronized,只要用了增强for循环再调用list.remove(),照样抛 -
ConcurrentHashMap的迭代器不检查modCount,所以它的forEach()或keySet().iterator()中调用remove()不会抛这个异常(但可能看不到最新修改) - 真正多线程下,
ArrayList还有数据错乱、越界等更隐蔽的问题,ConcurrentModificationException反而是最友好的提示
哪些操作会触发 modCount 变更
所有改变集合结构的方法都会自增 modCount,包括:
-
add()、remove()、addAll()、clear() -
retainAll()、removeAll()(哪怕没实际删任何元素,也改了modCount) - 但
set()不会 —— 它只替换元素内容,不改变结构,所以for (int i = 0; i 是安全的
容易被忽略的是:某些工具方法(如 Collections.synchronizedList() 包装后,仍用原生 Iterator,不解决这个问题;必须用其返回的 iterator() 才能配合 remove())









