collections.synchronizedmap的iterator不线程安全,因其仅同步单个方法,遍历是跨方法的复合操作,依赖fail-fast机制检测并发修改;正确做法是在同一锁下执行整个遍历,或改用concurrenthashmap等更优方案。

因为 Collections.synchronizedMap 的 iterator 本身不是线程安全的,它只是对单个方法调用做了同步,而遍历是一个跨多个方法调用(next()、hasNext())的复合操作,底层仍依赖 fail-fast 机制检测并发修改。
fail-fast 不等于线程安全
fail-fast 是一种错误检测策略,不是并发控制手段。它的作用是:当迭代器发现集合结构被其他线程修改(如 put 或 remove),就立即抛出 ConcurrentModificationException 来暴露问题,而不是掩盖或容忍不一致状态。
-
synchronizedMap确保每个put/get是原子的,但不保证迭代器与这些修改之间的协调 - 迭代器内部维护一个
modCount快照,每次调用next()都会比对当前 map 的实际modCount - 即使两个操作都加了锁,只要它们不是在同一个同步块内执行,
modCount就可能不一致
手动加锁是唯一能避免 CME 的方式
要让遍历过程“看到一致的快照”,必须让整个 for-each 循环运行在同一个锁保护下,阻止其他线程在遍历中途修改 map。
- 正确做法:
synchronized (syncMap) { for (Entry<k> e : syncMap.entrySet()) { ... } }</k> - 注意:锁对象必须是
syncMap本身(即包装后的 map),因为它的mutex才是所有方法同步所用的锁 - 不能只锁原始 map,也不能锁新创建的 entrySet 视图——它们不共享同一把锁
为什么 ConcurrentHashMap 不需要这样
它不依赖 modCount 和 fail-fast,而是采用弱一致性迭代器:
- 遍历时读取的是某一时刻的哈希桶快照,允许其他线程同时修改不同桶
- 不会抛
ConcurrentModificationException,也不会阻塞写操作 - 读操作无锁,遍历全程无需外部同步
实际开发中更推荐替代方案
与其手动加锁遍历,不如选用更适合并发场景的设计:
- 用
ConcurrentHashMap替代synchronizedMap,尤其在读写混合或高并发时 - 若只需读取全部内容,可先调用
new HashMap(syncMap)获取快照再遍历,避免长时持锁 - 对统计类场景,优先使用
computeIfAbsent、merge等原子方法,减少显式同步需求
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











