Collections.synchronizedMap适用于低并发、读多写少场景,单次基础操作(put/get/remove/clear)已同步无需额外加锁;但check-then-act和迭代遍历时必须手动synchronized(syncMap)加锁,因其使用全局独占锁,高并发写性能差且迭代中修改会抛ConcurrentModificationException。

Collections.synchronizedMap 是一种轻量、直观的线程安全包装方式,适合低并发、读远多于写的场景,但不能当作“开箱即用”的万能方案——关键在于理解它锁什么、什么时候要自己加锁。
什么时候可以直接用,不额外加锁
单次基础操作(如 put、get、remove、size)本身已同步,无需再包裹 synchronized 块:
- 应用启动时一次性加载配置项:syncMap.put("timeout", "3000")
- 运行中频繁读取静态字典:String country = syncMap.get("CN")
- 整个 map 清空或替换(极少发生):syncMap.clear()
这些操作内部已通过 synchronized(mutex) 保证原子性,直接调用即可。
什么时候必须手动加锁
两类情况绕不开显式同步:复合逻辑和迭代遍历。
立即学习“Java免费学习笔记(深入)”;
-
检查后写入类操作(check-then-act):比如“若 key 不存在才 put”,不能拆成两步调用,否则中间可能被其他线程插入相同 key
✅ 正确写法:synchronized (syncMap) { if (!syncMap.containsKey(k)) syncMap.put(k, v); } -
遍历整个 map(entrySet / keySet / values):iterator 不是线程安全视图
✅ 正确写法:synchronized (syncMap) { for (var e : syncMap.entrySet()) { ... } }
⚠️ 注意:锁对象必须是 syncMap 本身,不是它的 keySet 或 entrySet
底层锁机制与性能边界
synchronizedMap 使用的是全局独占锁,所有方法共用同一把锁(默认为包装器对象自身)。这意味着:
- 同一时刻只能有一个线程执行任何操作(读或写),高并发写会严重排队
- 哪怕只是 get,也会阻塞正在执行 put 的线程;反过来也一样
- 迭代期间其他线程修改 map,会触发 ConcurrentModificationException(fail-fast)
所以它不适合:高频写入(如计数器累加)、边遍历边更新、或要求弱一致性迭代器的场景。
与其他线程安全方案的配合建议
不是非此即彼的选择,而是按需组合:
- 团队已统一使用 Collections.synchronizedList/synchronizedSet → 对 map 也选 synchronizedMap,保持风格一致,降低维护成本
- 单元测试中快速构造共享数据容器 → 它比 ConcurrentHashMap 更易写、易懂,且无初始化开销
- 生产环境读多写少、QPS 不超过几百 → 可用;一旦压测发现 get/put 延迟明显上升,就该切换到 ConcurrentHashMap
不复杂但容易忽略。用对了省事,用错了反而埋下并发隐患。

















