迭代器弱一致性是并发容器的明确设计行为:迭代器创建后基于某个快照状态,后续写操作对其不可见且不抛concurrentmodificationexception。

迭代器弱一致性是什么现象?
它不是 bug,而是并发容器(比如 ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList)的明确设计行为:**迭代器创建后,看到的数据状态是“某个快照”,后续写操作对它完全不可见,也不会抛 ConcurrentModificationException。**
常见错误现象:
- 主线程拿到
iterator(),另一线程立刻add()或remove(),但遍历时既没新增项,也没跳过已删项,还“漏掉”或“多出”元素 - 调用
size()返回 5,但遍历只输出 4 个 —— 不是数据丢了,是快照和实时状态不一致 - 在循环中边遍历边
remove()(比如用for-each),结果删错、删漏、甚至静默失败(CopyOnWriteArrayList的迭代器remove()直接抛UnsupportedOperationException)
为什么不同容器都选弱一致性?
为了不锁住整个结构,让读操作能高并发执行。强一致性(比如 Vector + synchronized)要保证遍历中任何修改都被立即感知,就得全程加锁 —— 读写互斥,吞吐暴跌。
各容器实现思路不同,但目标一致:
-
CopyOnWriteArrayList:迭代器基于构造时的数组副本,写操作另起新数组,旧迭代器永远看不到新数组内容 -
ConcurrentLinkedQueue:迭代器按节点指针链式推进,但不检查中间节点是否已被其他线程 unlink;所以可能遍历到“逻辑已删但物理未回收”的节点 -
ConcurrentHashMap:迭代器按桶(segment / node table)顺序扫描,但不阻塞写线程重哈希或扩容;可能跳过刚插入的桶,也可能重复扫到迁移中的桶
哪些场景会踩坑?怎么绕开?
弱一致性本身没问题,问题出在误把它当“实时视图”用。以下操作必须警惕:
- 需要“遍历 + 条件删除” → 别用
for-each或手动it.remove(),改用容器自身提供的原子方法:removeIf()(CopyOnWriteArrayList支持)、computeIfPresent()(ConcurrentHashMap) - 需要“遍历中判断 size 是否变化” → 别依赖
size(),它只是近似值;真要校验,应在外层加读锁(如ReentrantReadWriteLock.readLock()),但这已脱离弱一致性初衷 - 监听器列表、配置项缓存等读远多于写的场景 →
CopyOnWriteArrayList是合理选择;但若写频繁(比如每秒上百次 add/remove),复制数组开销会明显拖慢性能 - 需要严格 FIFO 且不能漏消息的队列 →
ConcurrentLinkedQueue迭代器不适合做“批量消费”,应改用poll()循环,靠返回null判断空
最容易被忽略的一点
弱一致性 ≠ 数据丢失。所有写操作都成功了,只是迭代器不承诺让你“看见”。如果你的业务逻辑隐含了“遍历即快照一致性断言”(比如“遍历完所有在线用户就发通知”,但漏掉了刚上线的几个),那问题不在容器,而在设计假设错了 —— 这种场景本就不该依赖迭代器,而应引入版本号、时间戳或事件驱动机制来协调状态。









