Java迭代器的 fail-fast 机制——为什么遍历时不能直接 remove?

Java 入门指南:迭代器(Iterator) 迭代器(Iterator) 是一种行为型设计模式,属于设计模式之一,迭代器模式提供了一种方法来顺序访问一个聚合对象(如List、Set等)中各个元素,而不需要暴露该对象的内部表示。 阅读详情

最近也是在学习Go语言,然后发现Go没有这个机制因此你如果for...range和s = append(s[:i], s[i+1:]...)这种搭配起来就会出现错误,而Go不会显式的抛出这个错误需要你自己Debug出来,当然可能主要是Go的语言哲学主要是轻量,不会有这种检查机制

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (Integer i : list) {        // 增强 for,底层是迭代器
    if (i % 2 == 0) {
        list.remove(i);         // ❌ 用集合本身的方法删除
    }
}
// 抛出 java.util.ConcurrentModificationException

单线程也会抛这个异常!如果没有这个fail-fast机制那么就会:

步骤cursor数组实际状态next() 返回结果
10→1[1,2,3,4]1奇数,保留
21→2[1,2,3,4]2偶数 → 删!数组变 [1,3,4],但 cursor 仍 = 2
32→3[1,3,4]4⚠️ 元素 3 被跳过了!

这样就会导致迭代器里面的元素被跳过啥的,就会导致遍历不完全的,因此Java不允许这样的事情发生就有了这个机制。

删掉 2 后,3 从下标 2 挪到了下标 1,而 cursor 已经走到 2,于是直接读到了 4——元素 3 被静默漏掉。这种「算错但不报错」的 bug 最难排查。fail-fast(下一节的 modCount)就是为了把它从「静默算错」变成「当场抛异常」。

fail-fast机制实现的原理

增强for循环的本质其实就是基于迭代器的语法糖,

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    Integer i = it.next();   // ← 异常在 next() 里抛出
    if (i % 2 == 0) {
        list.remove(i);
    }
}

而这个异常的来源也是Integer i = it.next();这里抛出来的错误

fail-fast 靠 ArrayList 内部维护的一个计数器 modCount(modification count,记录结构性修改次数,如 add/remove)。

  • 调用 list.iterator() 时,迭代器把当前的 modCount 存进自己的 expectedModCount。
  • 每次 it.next() 都会调用 checkForComodification() 检查两者是否相等。
  • list.remove() 会让 modCount++,但不会更新迭代器的 expectedModCount → 两者不等 → 抛异常。
// ArrayList 源码(简化)
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

public E next() {
    checkForComodification();   // 每次取元素都校验
    // ... 返回元素
}

为什么不可以使用list.remove()这种方法呢?

关键点:expectedModCount 不是 ArrayList 的字段,而是迭代器(Itr)自己的私有字段。ArrayList 压根不知道它的存在,自然也改不了它。但是如果是迭代器自己去进行操作那么对应的索引就会自动对其,那么你就可以进行操作(是按照迭代器创建的时候的快照对其的)

变量属于谁作用
modCountArrayList(外部对象)记录集合被结构性修改的次数
expectedModCountIterator(迭代器实例,ArrayList 的内部类对象)迭代器创建那一刻,对 modCount 拍的「快照」
// list.remove():只改自己的 modCount,够不到迭代器的 expectedModCount
public boolean remove(Object o) {
    ...
    modCount++;   // ✅ 只动自己,没有任何一行去碰 expectedModCount
}

// it.remove():是"自己人",删完顺手把自己的快照也对齐
public void remove() {
    ArrayList.this.remove(lastRet); // 调用外部类删除,modCount++(如变成 5)
    expectedModCount = modCount;    // ✅ 自己同步,让两者重新相等
}

三层原因:

①够不到——list.remove() 执行时手里没有任何迭代器对象的引用,改不了它的私有字段;

②不知道改哪个——同一个 list 可同时存在多个迭代器,而 ArrayList 根本没维护「我有哪些迭代器」的列表;

③这就是本意——fail-fast 就是要让「绕过迭代器直接改集合」被发现并报错,如果list.remove() 真去同步了快照,fail-fast 反而失效了。

fail-fast 不能「防止」并发问题,而且主要是单线程中防止

fail-fast 不是线程安全机制,也不能解决并发问题——它防不住,也没打算防。它只是一个「尽力而为的检测器」。

根因还是一个细节:modCount 不是 volatile。

  • 真正的多线程场景下,一个线程对 modCount++ 的修改不保证对另一个线程立即可见(可能停在 CPU 缓存里)→ CME 可能漏检,也可能在不该抛时抛出。
  • 因此JDK 官方文档明确说:fail-fast 行为无法保证,绝不能编写依赖此异常来保证正确性的程序,它只应用于发现 bug。
  • 它真正的价值:在「边遍历边改」这种写错的代码(尤其单线程误用)中尽早暴露问题,而不是默默返回错乱数据让 bug 潜伏。

正确的删除方式

方式 A:用迭代器自己的 remove()(它会同步更新 expectedModCount)

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() % 2 == 0) {
        it.remove();  // ✅ 内部会执行 expectedModCount = modCount
    }
}

方式 B:removeIf()(JDK 8+,最简洁)

list.removeIf(i -> i % 2 == 0); // ✅ 推荐

和Go的对比与思考

Go 没有 Java 的 fail-fast(无 modCount / CME)。

Go 的range 不是对象,它是语言关键字,编译后就是一个朴素的下标/指针循环。slice 本身只是个轻量值(指针 + len + cap 三个字段),没有任何隐藏的簿记字段,也没有一个"迭代器对象"能让你往里塞 modCount。

而没有迭代器对象因此Go的for..range会更轻量但是也会导致没有这种提醒机制来防止你不小心写错代码,还是性能和语言哲学的取舍吧

SAP MIGO屏幕增强的具体实施步骤介绍(SE19:MB_MIGO_BADI) <转载> 1.MIGO抬头屏幕增强具体增强实施步骤1.1.创建存储抬头客制化数据的增强表1.2.创建增强结构1.3.创建函数组ZMM_MIGO1.3.1.定义全局变量1.3.2.创建数据处理函数1.4.创建字屏幕9001屏幕PBOendif.endloop.1.5.实施新BADI增强SE19创建BADI实施双击实施类ZCL_MIGO_EN_01exportingendif.endmethod.endif.endif.exportingendmethod. 阅读详情

相关推荐

Android AudioFlinger实战:如何用FastMixer优化低延迟音频流(附调试技巧)

本文深入解析了Android AudioFlinger的FastMixer线程机制,指导开发者如何利用该技术优化低延迟音频流。通过实战配置AAudio API、调整硬件缓冲区及线程优先级,并结合adb、Systrace等调试技巧,可将音频延迟显著降低至10毫秒以下,适用于音乐制作、游戏音效等实时音频处理场景。

weixin_29230901的博客 335

Java,你告诉我 fail-fast 是什么鬼?

本篇我们来聊聊 Java 的 fail-fast 机制,文字一如既往的有趣哦。

沉默王二 4036

基于光电传感的非接触式酒精浓度检测系统设计与实现

本文详细介绍了基于光电传感技术的非接触式酒精浓度检测系统设计与实现。系统利用940nm红外LED和OPT101光电传感器,结合朗伯-比尔定律,通过STM32微控制器进行信号采集与处理,实现了快速、准确的酒精浓度测量,测量精度达±1%,适用于工业检测和便携式应用场景。

y7z8a的博客 364

详解为什么在foreach中不能进行remove和add操作

今天早上小熙在关注的公众号中看到了这一篇不错的技术分享,特此实践顺便阐述下自己的理解。 在阿里开发手册上有这样一条规定: 阿里规范上没有给出详细解释。所以小熙就详细的说说我的借鉴和理解。 一. foreach循环 介绍: Java5之后引入的功能。使得遍历数组和集合更加简洁,无需获得数组和集合长度,无需根据索引来访问数组元素和集合元素。foreach循环自动遍历数组和集合的每个元素。所以通常...

人生到处知何似,应似飞鸿踏雪泥。 5508

Win11 24H2家庭版共享打印机失败?别慌,一个批处理脚本搞定所有设置

本文提供了Win11 24H2家庭版共享打印机失败的解决方案,通过一个批处理脚本自动修复所有设置问题。脚本包含安装组策略组件、修改关键注册表项等操作,帮助用户快速恢复打印机共享功能,适用于普通家庭用户。

weixin_30632089的博客 419

详解电路设计中的RC电路

​ 一、什么是RC电路 RC电路是指由电阻R和电容C组成的电路,它是脉冲产生和整形电路中常用到 的电路。 常见的RC充、放电电路、RC积分电路合RC微分电路接下来详细讲解。 二、RC充、放电电路 RC充、放电电路如下图所示,下面通过充电和放电两个过程来分析此电路。 ​RC充、放电电路图 (1)RC充电电路 RC充电电路图如下图(a)所示 RC充电电路图及充电曲线图 ​由上图分析: 将上图a中开关S置于“1”处,电源E开始通过电阻R对电容C充电,由于刚开始充电时电容两端没有..

IT xiaobai 4万+

Hashmap遍历时用map.remove方法为什么会报错?

private static HashMap<Integer, String> map = new HashMap<Integer, String>();; public static void main(String[] args) { for(int i = 0; i < 10; i++){ map.put(i, "value" + i); } } for(Map.Entry&l.

Asa_Prince的博客 2167

ASIC--DFT可测性设计工程师

DFT是什么? DFT是design for test(可测性设计)的缩写,就是在芯片设计过程中,加入可测性逻辑。有的公司把该职位归到前端设计,有的归到中端实现。 DFT职位大多分布于规模较大的数字IC设计公司里,因为大公司对芯片品质要求高,而且规模越大,芯片越贵,DFT就越复杂越重要。DFT主要是通过在芯片中加入可测性逻辑,等芯片制造出来,在ATE(AutomaticTestEquipmen...

TroubleMaker 2万+

如何用 OCLP 四步装回停更老 Mac 的 macOS:2007–2020 机型实战入门

苹果每发一个大版本,支持列表里就少一批 Mac,系统升不动、新应用开始要求更高的 macOS,老机器只能慢慢弃用。OpenCore Legacy Patcher(OCLP)是一个免费的开源工具,它靠 OpenCore 引导加载器在启动阶段向内存注入补丁,绕过苹果的机型限制。它能把你 2007 年左右的 Intel 机型装回 macOS Big Sur 到 Sequoia,全程只需要四个动作。 #

gitblog_00665的博客 1064

MATLAB闪退别慌!5分钟搞定Windows系统预设文件修复(附详细截图)

本文针对MATLAB在Windows系统下频繁闪退的问题,提供了快速有效的解决方案。核心在于修复或重置损坏的MATLAB预设文件,通过图文并茂的五步法,指导用户定位AppData目录、重命名预设文件夹,从而让MATLAB自动生成全新配置以解决闪退。文章还涵盖了问题诊断、设置备份及进阶排查方法,帮助用户彻底根除启动故障。

weixin_29234423的博客 925

【Bug已解决】Codex CLI 报错 You‘ve hit your usage limit 解决方案

Codex CLI 密集使用时触发 You've hit your usage limit 限流报错,本质是订阅套餐额度机制在正常发挥作用。文中给出等待滚动重置、拆分大任务、主动管理上下文、升级套餐、团队排队机制等完整解决方案。

博客 1万+

Java 集合如何实现 Fail-Fast 与 Fail-Safe?迭代器机制详解

Java集合框架中的Fail-Fast和Fail-Safe机制是处理并发修改的两种不同策略。Fail-Fast机制在检测到并发修改时会立即抛出ConcurrentModificationException,以防止数据不一致,常见于ArrayList、HashSet等集合。Fail-Safe机制则通过创建集合的副本来避免并发修改导致的异常,适用于CopyOnWriteArrayList和ConcurrentHashMap等集合。Fail-Fast通过维护modCount字段来实现,而Fail-Safe则通过写

小健学Java的博客 1139

SAP资产模块(FI-AA)与总账(FI-GL)数据不一致是财务顾问和会计人员最常遇到的棘手问题之一

SAP资产模块(FI-AA)与总账(FI-GL)数据不一致问题主要源于四大原因:1)违规手工记账(最常见),直接通过总账录入资产科目凭证而未通过资产模块;2)折旧运行问题,包括执行失败、跨年度问题或配置冲突;3)主数据与配置错误,如科目确定或折旧键设置不当;4)业务流程遗漏,如单据未生成凭证或汇率差异。解决方案包括运行对账报表、检查凭证来源、补跑折旧及核对配置。关键预防措施是坚持"资产模块发起→自动生成凭证→总账审核"的标准流程。

金牌架构师 428

集合遍历时修改导致Fail-Fast?一文搞懂Java迭代器安全删除机制

深入解析Java集合框架详解中迭代器的Fail-Fast机制,教你安全遍历并删除元素。涵盖ConcurrentModificationException成因、Iterator.remove()正确用法及多线程场景应对策略,掌握高效避坑技巧,值得收藏。

CodeVibe的博客 338

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

什么是fail-fast机制

Fail-Fast 机制是 Java 集合框架提供的一种快速检测迭代期间结构性修改的保护机制,通过modCount与迭代器的对比实现。其核心价值在于在开发和测试阶段及时发现不当的集合操作,但不能依赖它保证线程安全。单线程迭代时,如需修改集合,务必使用迭代器的remove()方法。多线程场景下,优先使用包中的并发集合,避免 Fail-Fast 异常和数据不一致问题。理解 Fail-Fast 机制,有助于写出更健壮的集合操作代码,避免因不当修改导致的隐性 bug。

埃泽漫笔 606

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Java中的fail-fast机制

1.问题由来 阿里开发规范里有一条:不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。这是为什么呢?看下面一个Demo: public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); ...

持之以恒,方得始终 1862

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Java基础面试题05:简述快速失败(fail-fast)和安全失败(fail-safe)的区别 ?

fail-fast是基于集合本体操作,检测到修改会抛异常,适用于单线程操作。fail-safe是基于集合副本操作,允许修改,但无法反映最新状态,适用于并发环境。最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。这是大佬写的7701页的BAT大佬写的刷题笔记,让我offer拿到手软本文,已收录于,我的技术网站cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享。

程序员江小北 1642

Java 中的 Fail-Fast 与 Fail-Safe

缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。会,问题与上面的有些相似,删除最后一个元素后,数组 size 变成 3,cursor 是 4,hasNext() 判断不相等,会再次进入循环,则抛出异常。采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

野蛮女孩的博客 756

java list中删除元素用remove()报错的fail-fast机制原理以及解决方案

原文:https://blog.youkuaiyun.com/lululove19870526/article/details/70808903 java list中删除元素用remove()报错的fail-fast机制原理以及解决方案 现在有一个list,有6个元素,值分别是1、5、5、8、5、10,现需要删除值为5的元素 第一种 import ja...

xin.wang的博客 1758

Java与Python集合遍历时并发修改异常与迭代器失效的实战解决方案

在编程中,集合(Collection)是存储和操作数据的核心数据结构,如Java的ArrayList、HashMap和Python的list、dict。其内部通常采用迭代器(Iterator)模式进行遍历,这是一种设计模式,用于顺序访问集合元素而无需暴露底层表示。然而,当在遍历过程中直接对集合进行结构性修改(如增删元素)时,会破坏迭代器的内部状态一致性,导致快速失败(fail-fast)机制触发或迭代器失效。这一机制旨在防止数据不一致,是保障程序健壮性的重要设计。从技术价值看,理解并规避此问题能显著提升代码

weixin_30954265的博客 390

孤舟笔记 基础篇十二 集合遍历时删元素就报错?fail-fast 和 fail-safe 到底在保护谁

Java集合的fail-fast和fail-safe机制对比:fail-fast通过modCount计数器检测修改,一旦发现立即抛出ConcurrentModificationException,保证数据强一致性,代表类如ArrayList;fail-safe则通过操作副本实现安全遍历,不会抛异常但可能读到旧数据,具有弱一致性,代表类如CopyOnWriteArrayList。关键区别在于fail-fast直接操作原数据,性能高但严格;fail-safe操作副本,适合并发场景但存在拷贝开销。单线程推荐使用f

学无止境 (专注java,爱好前端) 660

遍历时删元素为什么报错:modCount 这个“计数器“在背后盯着你

遍历集合的时候不能直接调用集合的remove方法”,这条经验大家都知道,但问一句"为什么会报错、报的又是什么异常",很多人只记得一个笼统的印象。这篇把Iterator和fail-fast机制背后的modCount计数器讲清楚,顺便说说这套机制到底能不能完全依赖。

NPE2.0的博客 598

java fail-fast机制

fail-fast的解释 在系统设计中,快速失效是一种可以立即报告任何可能表明故障的机制。快速失效通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这样做的好处是可以预先识别出一些错误情况: 一方面可以避免执行复杂的其他代码; 另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。 Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当我们使用i...

赶路人儿 370

fail-fast快速失败机制

fail-fast俗称快速失败,是在多线程进行迭代操作时产生冲突的一种异常抛出机制,下面我们就由ArrayList来深入理解Java中的fail-fast机制. 1. fail-fast简介 “快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。某个线程在对collection进行迭代时,不允许其他线程对该collection进行结构上的修改。 例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,

码农翻身的博客 638

Java 遍历时删元素抛 ConcurrentModificationException:fail-fast 原理与三种正确删法

线上有段代码,把一批订单里已经取消的挑出来删掉,平时跑得好好的,某天量一大就抛,而且异常里没有任何和「并发」相关的线索——明明整段代码就一个线程在跑。这个异常名字里带 Concurrent,却经常在单线程里出现,坑了不少人。这篇把它的来龙去脉讲清楚,再给三种能落地的正确删法。

weixin_42662753的博客 222

【大白话说Java面试题】【Java基础篇】第2题:Iterator的fail-fast和fail-safe机制有什么区别?

摘要: fail-fast机制(如ArrayList)通过modCount检测并发修改,遍历时若集合被修改会抛出异常;fail-safe机制(如CopyOnWriteArrayList)通过数据副本来实现安全遍历,允许并发修改但遍历的是旧快照。区别在于fail-fast强调实时一致性,而fail-safe牺牲一致性换取并发安全。实际应用中,读多写少选fail-safe(如CopyOnWriteArrayList),写多读少或单线程场景可用fail-fast集合,但需注意避免遍历时直接修改集合。

Java后端 | AI智能体应用实战派 | 电脑技能行家 278
上一篇: DCL 双重检查锁——为什么必须使用 volatile?
下一篇: UU远程(UURemote)连接超时问题排查全记录
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值