ConcurrentHashMap 的 keySet 迭代器是弱一致性而非 fail-safe:不加锁、不阻塞修改,可能跳过新插入元素或重复遍历,也不保证看到所有已提交修改。

ConcurrentHashMap 的 keySet 迭代器**不是 fail-safe,而是弱一致性(weakly consistent)**。它既不抛 ConcurrentModificationException,也不提供快照语义——这常被误读为“安全失败”,实则是一种设计取舍:牺牲强一致性,换取高并发吞吐。
keySet 迭代器的弱一致性表现
调用 map.keySet().iterator() 得到的迭代器,在遍历过程中:
- 不会加锁,也不阻塞其他线程的 put/remove 操作
- 可能跳过刚插入但尚未对当前遍历线程可见的元素(尤其在扩容或链表结构变更时)
- 可能重复遍历某些节点(例如某桶正在迁移,迭代器先后访问了旧桶和新桶)
- 无法保证看到所有已提交的修改,包括已成功返回的
put("FIVE", 5)
为什么不是真正的 fail-safe
Fail-safe 的典型实现(如 CopyOnWriteArrayList)会在迭代前复制整个底层数组,后续修改只作用于原集合,不影响副本。而 ConcurrentHashMap:
- 不复制数据,迭代直接读原始
Node[] table和链表/红黑树结构 - 依赖
volatile字段(如Node.value、next)和 CAS 保证部分可见性,但无全局一致性保障 - 没有
modCount机制,也无版本号或快照标记,因此不满足 fail-safe 的定义
验证弱一致性的简单代码示例
以下行为可复现弱一致性:
立即学习“Java免费学习笔记(深入)”;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("ONE", 1);
map.put("TWO", 2);
// 启动遍历线程
Thread iterThread = new Thread(() -> {
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
System.out.println("iter: " + it.next());
// 故意延时,增大观察窗口
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
});
// 同时插入新元素
Thread putThread = new Thread(() -> {
try { Thread.sleep(5); } catch (InterruptedException e) {}
map.put("THREE", 3);
map.put("FOUR", 4);
map.put("FIVE", 5); // 这个很可能不被 iterThread 打印出来
});
iterThread.start();
putThread.start();
运行多次会发现:FIVE 偶尔出现、偶尔缺失——这不是 bug,而是弱一致性的正常表现。
get 与迭代的一致性边界
值得注意的是:get(key) 本身也是弱一致的。即使 put("FIVE", 5) 已返回,紧随其后的 get("FIVE") 仍可能返回 null,因为:
- 写操作更新的是某个桶的链表头节点,其
volatile next或value的可见性依赖 happens-before 链 - 迭代器按数组索引顺序扫描,若插入发生在尚未遍历到的桶中,且该桶尚未完成结构刷新,则跳过
- 扩容期间节点迁移未完成时,读线程可能从旧表或新表读取,导致结果不可预测


















