如何解析fail-fast与fail-safe迭代器在处理集合并发修改时的底层原理差异?
- 内容介绍
- 文章标签
- 相关推荐
本文共计699个文字,预计阅读时间需要3分钟。
区分fail-fast与fail-safe:
前者靠 modCount 和 expectedModCount 的比对触发 ConcurrentModificationException;后者如 CopyOnWriteArrayList 的迭代器,一创建就复制当前数组引用,后续所有 next() 都在副本上走,原集合增删改完全不影响迭代过程。
单线程下也会触发 ConcurrentModificationException?
会,而且很常见。这不是多线程专属问题,而是结构修改与迭代器生命周期冲突的必然结果。
- 用
for-each遍历ArrayList时调用list.remove(x)→ 触发异常 - 在
Iterator.hasNext()和next()之间,另一个方法修改了集合 → 触发异常 -
HashMap的entrySet().iterator()遍历时,外部调用put()或remove()→ 触发异常
关键点:只要不是通过迭代器自身的 remove() 方法修改,就可能踩中 modCount 不一致的检查点。
ConcurrentHashMap 的迭代器真是 fail-safe 吗?
不是严格意义上的“快照”,而是弱一致性(weakly consistent)视图。它不抛 ConcurrentModificationException,但行为和 CopyOnWriteArrayList 有本质不同:
-
ConcurrentHashMap迭代器不复制整个哈希表,而是按段(segment)或桶(bin)逐步访问,可能看到部分更新、部分未更新的数据 - 遍历过程中插入的新键值对,可能被看到,也可能看不到——这取决于插入时机和分段锁状态
- 不会因并发修改崩溃,但也不保证看到“某一时刻的完整快照”
所以它的文档明确写的是 “weakly consistent”,而非 “fail-safe”;而 CopyOnWriteArrayList.iterator() 才是真正基于不可变快照的 fail-safe 实现。
为什么不能把 fail-fast 当作并发控制手段?
因为 modCount 检查只是尽力而为的调试辅助,不是同步机制:
- 检查只发生在
next()、remove()等少数节点,中间修改可能逃逸 - 多个线程同时修改时,
modCount自增可能丢失(非原子操作),导致异常根本没抛出来 - 即使抛了
ConcurrentModificationException,也只说明“发生了未预期修改”,不提供任何回滚或重试能力
真正需要并发安全,得用 ConcurrentHashMap、CopyOnWriteArrayList,或者加显式锁——fail-fast 的唯一正经用途,就是帮你早点发现遍历时误改集合的 bug。
本文共计699个文字,预计阅读时间需要3分钟。
区分fail-fast与fail-safe:
前者靠 modCount 和 expectedModCount 的比对触发 ConcurrentModificationException;后者如 CopyOnWriteArrayList 的迭代器,一创建就复制当前数组引用,后续所有 next() 都在副本上走,原集合增删改完全不影响迭代过程。
单线程下也会触发 ConcurrentModificationException?
会,而且很常见。这不是多线程专属问题,而是结构修改与迭代器生命周期冲突的必然结果。
- 用
for-each遍历ArrayList时调用list.remove(x)→ 触发异常 - 在
Iterator.hasNext()和next()之间,另一个方法修改了集合 → 触发异常 -
HashMap的entrySet().iterator()遍历时,外部调用put()或remove()→ 触发异常
关键点:只要不是通过迭代器自身的 remove() 方法修改,就可能踩中 modCount 不一致的检查点。
ConcurrentHashMap 的迭代器真是 fail-safe 吗?
不是严格意义上的“快照”,而是弱一致性(weakly consistent)视图。它不抛 ConcurrentModificationException,但行为和 CopyOnWriteArrayList 有本质不同:
-
ConcurrentHashMap迭代器不复制整个哈希表,而是按段(segment)或桶(bin)逐步访问,可能看到部分更新、部分未更新的数据 - 遍历过程中插入的新键值对,可能被看到,也可能看不到——这取决于插入时机和分段锁状态
- 不会因并发修改崩溃,但也不保证看到“某一时刻的完整快照”
所以它的文档明确写的是 “weakly consistent”,而非 “fail-safe”;而 CopyOnWriteArrayList.iterator() 才是真正基于不可变快照的 fail-safe 实现。
为什么不能把 fail-fast 当作并发控制手段?
因为 modCount 检查只是尽力而为的调试辅助,不是同步机制:
- 检查只发生在
next()、remove()等少数节点,中间修改可能逃逸 - 多个线程同时修改时,
modCount自增可能丢失(非原子操作),导致异常根本没抛出来 - 即使抛了
ConcurrentModificationException,也只说明“发生了未预期修改”,不提供任何回滚或重试能力
真正需要并发安全,得用 ConcurrentHashMap、CopyOnWriteArrayList,或者加显式锁——fail-fast 的唯一正经用途,就是帮你早点发现遍历时误改集合的 bug。

