arraydeque 的 fail-fast 机制不依赖 modcount,而是通过比较迭代器持有的数组引用与 deque 当前 elements 引用是否一致来检测结构性修改;扩容、缩容、clear 等导致数组引用变更的操作会触发 concurrentmodificationexception。

ArrayDeque 的迭代器确实实现了 fail-fast 机制,但它的 expectedModCount 校验方式与 ArrayList、HashMap 等容器不同:它不基于全局修改计数器(modCount),而是直接检查底层数组是否被结构性修改——即通过比较当前迭代器持有的数组引用与 deque 当前的数组引用是否一致。
为什么 ArrayDeque 不用 modCount?
ArrayDeque 是无界双端队列,内部使用循环数组实现,所有结构性操作(如 addFirst、addLast、removeFirst、removeLast)在必要时会触发数组扩容或缩容,此时会创建新数组并复制元素。旧数组不再被 deque 引用,但迭代器仍持有对旧数组的引用。
迭代器在每次调用 next() 或 hasNext() 时,会执行如下校验:
- 检查当前持有的数组引用是否仍等于 deque 实例的
elements字段值 - 若不等,说明发生了扩容/缩容或 clear() 导致数组替换,立即抛出
ConcurrentModificationException - 该判断不依赖整型计数器,避免了 modCount 溢出或并发下计数器失效的问题
哪些操作会触发 fail-fast?
只要导致 elements 数组引用变更的操作,都会使正在运行的迭代器失效:
-
addFirst(e)/addLast(e)触发扩容(原数组满时) -
removeFirst()/removeLast()触发缩容(元素数 ≤ 容量/4 且容量 > 8) -
clear()直接置空并重置数组(可能分配新空数组) -
toArray()不影响,因不修改 elements 引用
fail-fast 不覆盖所有并发场景
该机制仅检测“数组引用变更”,无法发现以下情况:
- 同一数组内元素被非迭代器线程修改(如通过索引赋值),不会触发异常
- 多个线程同时调用
addLast()但未引起扩容,也不会报错(但结果不可预测) - 迭代器自身调用
remove()是安全的,它会同步更新迭代器状态并校验数组引用
如何安全地边遍历边修改?
若需在遍历时增删元素,推荐以下方式:
- 使用迭代器自身的
remove()方法(仅支持删除当前元素) - 收集待操作元素,遍历结束后统一处理(如先记下要删的索引,再倒序删除)
- 改用线程安全容器(如
ConcurrentLinkedDeque),但注意它不提供强一致性迭代器 - 加锁保护整个遍历+修改过程(如 synchronized 块包裹 deque 和迭代逻辑)
ArrayDeque 的 fail-fast 是轻量且精准的——它不靠计数器,而靠数组引用快照,既规避了整数溢出风险,又准确捕获了真正影响迭代一致性的结构变更。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











