PriorityQueue 的迭代器是 fail-safe 而非 fail-fast,它通过构造时复制数组快照实现弱一致性,不校验 modCount,遍历时完全基于副本,因此不会抛 ConcurrentModificationException。

Java 中 PriorityQueue 的迭代器(Itr)确实实现了 fail-fast 机制,但它**不校验 modCount** —— 这是关键点。
PriorityQueue 的 Itr 不基于 modCount 实现 fail-fast
PriorityQueue 的内部迭代器 Itr 是一个弱一致性(weakly consistent)迭代器,它不继承 AbstractIterator 或使用 modCount/expectedModCount 模式。与 ArrayList、HashMap 等容器不同,PriorityQueue 的迭代器在构造时会复制当前堆数组的快照(snapshot),后续遍历操作完全基于这个副本进行。
这意味着:
- 迭代过程中即使队列被结构性修改(如
add、poll、remove),原数组和快照已分离,不会触发ConcurrentModificationException; - 迭代器不会读取或比较
modCount,其源码中根本不存在expectedModCount字段或相关校验逻辑; - 它不保证反映迭代期间的实时修改,但也不会因并发修改而抛异常 —— 属于“fail-safe”行为,而非 “fail-fast”。
为什么 PriorityQueue 选择快照而非 modCount 校验
根本原因在于 PriorityQueue 的底层是可变长度的 Object[] 数组 + 堆调整逻辑,其结构性修改(尤其是 poll() 或 add())常伴随数组缩容、元素重排、甚至数组重建(如扩容)。若强行用 modCount 校验:
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 每次堆调整都需更新
modCount,但堆操作本身频繁且内部细节复杂(如siftDown、siftUp),难以精准界定“结构性修改”的边界; - 迭代器若持引用到原数组,遍历时可能遇到
null元素、越界或数据错乱(因堆结构动态变化),风险高于抛异常; - 快照方式更简单可靠:构造时
Arrays.copyOf(queue, size),后续只读副本,天然线程安全(针对该迭代器自身)且无并发校验开销。
对比典型 fail-fast 迭代器(如 ArrayList$Itr)
以 ArrayList 为例,其 Itr 在创建时记录 expectedModCount = modCount,每次 next() 或 remove() 前都会检查 modCount == expectedModCount,不等则抛异常。
而 PriorityQueue$Itr 的 next() 方法只操作本地快照数组和计数器 cursor,核心逻辑类似:
public E next() {
if (cursor >= size) throw new NoSuchElementException();
return (E) snapshot[cursor++]; // 完全不碰 queue[] 或 modCount
}没有 checkForComodification() 调用,也没有任何对 modCount 的访问。
实际开发中的注意事项
使用 PriorityQueue.iterator() 时需明确:
- 它不是 fail-fast 的,不能依赖它检测并发修改;
- 迭代结果反映的是创建迭代器那一刻的队列状态,后续增删不影响本次遍历,但也看不到新加入/移除的元素;
- 若需强一致性或实时性,应自行同步访问(如用
Collections.synchronizedQueue(new PriorityQueue())),或改用线程安全的替代品(如ConcurrentLinkedQueue,但注意它不保证优先级顺序); - 不要误以为 “有迭代器” 就等于 “支持 modCount 校验” —— 行为由具体实现决定,必须查源码或文档确认。

















