synchronizedMap适合读多写少、操作粒度粗的静态配置缓存场景,如国家代码表、HTTP状态码映射;需手动同步复合操作;与synchronizedList等统一风格使用;不适用于高并发写或频繁迭代。

什么时候适合用 synchronizedMap
它最适合那些启动时初始化、之后几乎只读的配置类场景。比如国家代码表、HTTP状态码映射、系统开关配置等——写操作集中在应用启动阶段,运行时 99% 是 get 查询。这种情况下,每次 get 虽然加锁,但几乎没有线程竞争,性能损耗微乎其微;而少量 put 因为频率极低,也不会拖慢整体响应。
如果项目里已经大量用了 Collections.synchronizedList 或 synchronizedSet,继续用 synchronizedMap 能保持同步风格统一,降低团队理解成本和排查复杂度。尤其在测试代码中快速构造线程安全容器时,它比引入 ConcurrentHashMap 更轻量、更直观。
哪些操作必须手动加锁
synchronizedMap 只保证单个方法(如 put、get、remove)是原子的,不保证多个方法组合起来仍安全。常见陷阱包括:
- 先检查再插入:
if (!map.containsKey(k)) map.put(k, v)—— 中间可能被其他线程抢先写入,导致重复或覆盖 - 读取后计算再更新:
v = map.get(k); map.put(k, v * 2)—— 两次调用之间值可能已被修改 - 遍历整个 map:
for (Entry e : map.entrySet())—— 迭代器本身不带同步,若此时有线程修改 map,会直接抛 ConcurrentModificationException
这些情况都得显式用 synchronized (map) { ... } 包裹整段逻辑,锁住整个 map 实例,才能确保复合操作的原子性。
立即学习“Java免费学习笔记(深入)”;
怎么避免常见误用
关键不是“用了 synchronizedMap 就万事大吉”,而是守住三条底线:
- 引用不可泄露:包装后的 syncMap 必须声明为 final,且不能把原始 HashMap 对象再暴露出去,否则绕过包装器的操作完全不受保护
- 不创建非同步视图:不要对 syncMap 调用 keySet()、entrySet() 后直接遍历,也不要用它们做子集操作(如 subMap),这些返回的集合不继承同步行为
- 不混用并发集合:别在一个模块里既用 synchronizedMap 又用 ConcurrentHashMap,两者锁机制、迭代器语义、空值处理都不同,容易引发难以复现的并发问题
它和 ConcurrentHashMap 怎么选
不是“哪个更好”,而是“哪个更合适”:
- 如果你的 map 每秒要承受上百次 put/remove,或者常需要边遍历边更新(比如定时清理过期项),synchronizedMap 的全局锁会迅速成为瓶颈,此时 ConcurrentHashMap 是唯一合理选择
- 如果你只是缓存静态字典、环境变量或配置项,且并发写极少,synchronizedMap 写法简洁、语义清晰、调试友好,没必要为理论上的性能优势引入额外复杂度
- 注意:ConcurrentHashMap 不支持 null 键或值,而 synchronizedMap 的行为取决于底层 Map(如 HashMap 允许 null 值),这点在迁移时要验证


















