collections.synchronizedmap遍历必须手动加锁,根本原因是entryset()等返回非同步视图,其迭代器无同步保障;调用entryset()方法本身线程安全,但返回的set及其iterator未加锁,遍历时若map被修改会触发concurrentmodificationexception。

Collections.synchronizedMap 的遍历必须手动加锁,根本原因在于:它只对单个方法调用做同步,而 entrySet()、keySet()、values() 返回的是非同步视图,其迭代器本身不带任何线程安全保证。
为什么 entrySet() 不是线程安全的?
synchronizedMap 仅在每个 public 方法(如 get、put、size)内部加了 synchronized(mutex),但 entrySet() 方法返回的是一个包装后的 Set 视图——这个视图底层仍使用原始 map 的数据结构,它的 iterator() 调用不会自动触发同步。也就是说:
- 调用 syncMap.entrySet() 这一步是线程安全的(方法本身被同步)
- 但拿到的 Set 对象及其 Iterator 并未被锁定
- 一旦其他线程在你遍历时修改 map(比如 put 或 remove),就会触发 ConcurrentModificationException
- 即使只是读操作,也可能因中间状态不一致导致漏项或重复
锁对象为什么必须是 syncMap 本身?
synchronizedMap 内部使用一个专用 mutex 对象(通常是 this,即包装后的 map 实例)作为锁。只有用同一个锁对象才能串行化所有访问:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- ✅ 正确:
synchronized (syncMap) { for (var e : syncMap.entrySet()) { ... } } - ❌ 错误:
synchronized (syncMap.keySet()) { ... }—— keySet() 是非同步视图,不是锁对象 - ❌ 错误:
synchronized (new Object()) { ... }—— 锁不匹配,完全无效
不加锁遍历的实际风险
哪怕业务逻辑只是“读取全部配置项”,只要存在任何写操作并发发生,就可能出问题:
- 遍历中途另一个线程执行了 syncMap.put("new.key", "value") → 抛 ConcurrentModificationException
- 多个线程同时遍历 + 同时写入 → 某些条目可能被跳过,或同一项被重复处理
- 极端情况下,因 fail-fast 机制未及时触发,读到部分更新、部分未更新的混合状态
替代方案更合适的情况
如果遍历频繁、或读写混合程度高,synchronizedMap 就显得力不从心:
- ConcurrentHashMap 提供弱一致性迭代器,遍历时允许并发修改,不抛 CME
- 若需强一致性快照,可先调用
new HashMap(syncMap)获取副本,再在外围遍历 - 纯只读场景下,启动时构建不可变 Map(如 Collections.unmodifiableMap)更轻量、更安全
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










