ConcurrentLinkedDeque迭代器既非fail-fast也非fail-safe,而是弱一致性:不抛ConcurrentModificationException,也不提供快照语义,允许并发修改且遍历永不中断,但可能跳过或重复访问元素。

ConcurrentLinkedDeque 没有 fail-safe 机制,它压根不提供迭代器的“快照”语义,也不在遍历时复制数据或加锁保护结构。它的迭代器是 weakly consistent(弱一致性)的,这是 J.U.C 并发集合的明确设计选择,而非缺陷。
ConcurrentLinkedDeque 的迭代器不 fail-safe,但也不 fail-fast
它既不会像 ArrayList 或 LinkedList 那样在检测到并发修改时抛出 ConcurrentModificationException(即非 fail-fast),也不会像 CopyOnWriteArrayList 那样返回创建时的副本(即非 fail-safe)。它的迭代器允许:
- 遍历时其他线程可自由增删节点,无同步开销
- 可能跳过刚插入的元素,也可能重复访问已删除又重连的节点
- 不保证看到所有当前存活元素,也不保证只看到存活元素
- 遍历过程永远不会抛异常,也不会阻塞
CAS 遍历如何做到“安全但不精确”
底层遍历依赖 head/tail 节点和 volatile Node 引用,配合 CAS 更新与 lazySetNext 等无锁操作。关键点在于:
- 每个 Node 的
item和next字段都是 volatile 修饰,确保可见性 - 遍历时通过
succ()(后继)或pred()(前驱)方法跳转,这些方法会主动跳过 item == null 的逻辑删除节点 - 即使某个节点在遍历中途被 unlink(如被 removeFirst/removeLast),只要引用尚未被 GC 回收,succ() 仍能安全前进——因为链表指针未被破坏,只是标记为“不可见”
- 没有全局版本号(modCount),所以无法判断“是否被修改”,自然也就无法触发 fail-fast 或构建 snapshot
为什么不能用 fail-safe 方式实现?
ConcurrentLinkedDeque 的核心目标是低延迟、高吞吐、无锁。fail-safe 通常意味着:
立即学习“Java免费学习笔记(深入)”;
- 复制整个链表结构(内存爆炸,且违背无界队列初衷)
- 或加读锁/写锁(破坏无阻塞特性)
- 或维护额外快照元数据(增加 CAS 复杂度与缓存行竞争)
这些都与其设计哲学冲突。JDK 明确要求它的迭代器“可容忍模糊性”,适用于监控、日志采样、批量通知等对强一致性无要求的场景。
开发者该怎么做?
若业务需要确定性遍历结果(例如必须处理每个元素且仅一次),不应依赖 ConcurrentLinkedDeque 迭代器,而应:
- 改用
ArrayDeque+ 外部显式同步(适合低并发、小规模) - 用
CopyOnWriteArrayList(适合读多写少、元素量可控) - 自行封装:先
toArray()获取快照(注意 size() 不准确,需循环 pollFirst 直到为空再收集) - 接受弱一致性,在业务逻辑中容忍“漏掉或重访”,比如幂等消费、状态轮询
它的安全性体现在“不会崩溃、不会死锁、不会数据损坏”,而不是“每次遍历都精确反映某一时刻全貌”。理解这点,才能用对地方。


















