Iterator 与 fail-fast 机制:你不知道的细节

张开发
2026/4/4 3:40:43 15 分钟阅读
Iterator 与 fail-fast 机制:你不知道的细节
点击上方“java大数据修炼之道”, 选择“设为星标”技术干货发文后 第一时间奉上引言你在用 for-each 遍历集合的时候有没有想过一个问题如果遍历过程中用集合的 remove 方法删除一个元素会发生什么很多人会说没事我用的是 Iterator。但实际上这里隐藏着一个可能导致程序崩溃的坑。今天我们就来彻底搞清楚 Iterator 和 fail-fast 机制的原理。一、for-each 循环的真相很多人以为 for-each 是另一种遍历方式实际上它只是 Iterator 的语法糖。编译时for-each 会被自动转换成 Iterator 调用你写的 for-each 代码经过编译器转换后实际上等价于调用 iterator() 方法获取 Iterator 对象然后通过 hasNext() 和 next() 来遍历。理解这一点是理解后面所有内容的基础。二、fail-fast 是什么fail-fast 是 Java 集合框架的一种错误检测机制。当多个线程并发修改集合或者在遍历过程中单线程修改集合fail-fast 机制会立即抛出 ConcurrentModificationException而不是等到遍历结束才报错。fail-fast 的核心依赖是一个叫 modCount 的计数器。每当集合结构发生变化添加、删除元素或者改变容量modCount 就会加一。Iterator 在创建时会记录当时的 modCount 值每次调用 next() 时都会检查 modCount 是否被改变过如果变了就抛异常。三、单线程也会触发 fail-fastfail-fast 不是多线程独有的单线程下也完全可能触发。比如这个经典错误用 for-each 遍历 List 的同时调用 list.remove()就会立即抛出 ConcurrentModificationException。这是因为 for-each 背后的 Iterator 检测到 modCount 在遍历过程中变了。那为什么下面的代码不会抛异常因为 iterator.remove() 是 Iterator 自己提供的安全删除方法它在删除元素后会把 expectedModCount 同步更新所以不会触发 fail-fast 检测。四、为什么这样设计有人会问为什么要用 fail-fast为什么不直接保证线程安全答案是fail-fast 本身就是一种快速失败的妥协方案它并不是用来保证线程安全的。fail-fast 的设计初衷是在开发阶段帮助开发者发现并发修改的问题。如果你在单线程下都会犯这个错误那多线程环境下一定会有更严重的问题。fail-fast 不能保证线程安全它只能尽力检测到并发修改而且结果是不确定的——有时候抛异常有时候不抛。所以千万不要把 fail-fast 当成线程安全机制来用。五、Iterator.remove() 的正确用法安全删除集合元素的正确姿势是在 Iterator 遍历过程中使用 iterator.remove()。正确用法如下先用 iterator 找到要删除的元素然后调用 iterator.remove()这样 modCount 和 expectedModCount 会保持同步不会触发 fail-fast。整个过程在遍历过程中完成不会漏掉元素也不会抛异常。还有一点要注意iterator.remove() 删除的是 lastReturned 指向的元素而不是当前迭代位置之后的所有元素。每次 remove 之前必须先调用 next()否则会抛 IllegalStateException。六、ListIterator 的高级用法ListIterator 是 Iterator 的子接口专门针对 List。它支持双向遍历还能修改元素。ListIterator 可以往前遍历hasPrevious也能往后遍历hasNext还能在遍历过程中替换元素set或者添加元素add。如果你需要在遍历 List 时往前回退或者在遍历过程中添加元素ListIterator 是唯一的选择。七、多线程下的正确做法如果多个线程会并发修改同一个集合fail-fast 完全靠不住必须使用真正的线程安全集合CopyOnWriteArrayList 是一个不错的选择它在每次修改时都会复制一份底层数组读操作不需要加锁写操作加锁并复制。它的迭代器遍历的是创建迭代器时的快照所以遍历过程中其他线程的修改不会影响当前遍历也不会抛 ConcurrentModificationException。另外如果多线程访问的是只读的共享集合可以用 Collections.unmodifiableList() 包装防止意外修改。八、实际开发中的建议第一永远不要在遍历集合的过程中用集合自身的 remove 方法。应该用 iterator.remove()。第二如果要按条件删除多个元素强烈推荐使用 removeIf() 方法这是 JDK 8 引入的底层帮你处理了 Iterator 的正确使用方式。第三多线程环境下不要用普通的 ArrayList、HashMap用 CopyOnWriteArrayList、ConcurrentHashMap 或者给普通集合加锁。第四理解 fail-fast 的目的它是开发阶段的辅助工具不是生产环境的线程安全保证。不要依赖它来做并发控制。九、面试加分点fail-fast 和 fail-safe 的区别fail-fast 在遍历过程中检测到并发修改就立即抛异常fail-safe 则通过复制底层数据结构来避免异常。ConcurrentHashMap、CopyOnWriteArrayList 都是 fail-safe 的典型代表。fail-safe 的代价是可能读到旧数据因为它遍历的是快照。总结fail-fast 是 Java 集合框架的一种快速失败机制通过 modCount 计数器在遍历时检测并发修改发现异常立即抛 ConcurrentModificationException。它的设计目的是帮助开发阶段发现问题而不是保证线程安全。记住三句话遍历时不要用集合的 remove用 iterator.remove()多线程要用并发安全集合fail-fast 是辅助工具不是线程安全保证。end往期精彩文章复习回顾1.SpringBoot 插件化开发模式真香啊 2.一行代码实现请假审批流程Java版 3.血泪教训8 个线程池最佳实践和坑 4.SpringBoot骚操作一个注解秒杀所有类型的文件下载 5.Controller层代码这么写同事们都模仿起来了最近整理一份资料《程序员学习手册》覆盖了 Java技术、面试题精选、操作系统基础知识、计算机基础知识、Linux教程、计算机网络等等。获取方式点“在看关注公众号Java大数据修炼之道并回复PDF领取更多内容陆续奉上。长按识别下方二维码关注后回复关键字:PDF领取你想学的java知识这里都有,长按下方图片识别关注我们吧~如喜欢本文请点击右上角把文章分享到朋友圈 因公众号更改推送规则请点“在看”并加“星标”第一时间获取精彩技术分享 点分享点收藏点在看

更多文章