collections.synchronizedmap()仅保证单个方法原子性,无法解决复合操作竞态条件;遍历时必须手动同步,否则抛concurrentmodificationexception;相比concurrenthashmap,其全表锁性能差且不支持高并发。

为什么 Collections.synchronizedMap() 不能直接解决并发写问题
它只是给原始 Map 加了一层同步包装,所有单个方法(如 get()、put())调用会串行执行,但复合操作(比如“检查是否存在再插入”)依然可能出错。典型场景是 if (!map.containsKey(key)) map.put(key, value); —— 这两步之间没有原子性,两个线程可能同时通过检查,最终只保留一个 put 结果。
Collections.synchronizedMap() 的正确使用姿势
必须手动对多步操作加锁,且锁对象得是包装后的 map 自身(不是原始 map)。否则锁不生效。
- 获取包装后的 map:
Map<string integer> syncMap = Collections.synchronizedMap(new HashMap());</string> - 执行复合逻辑时,显式同步:
synchronized (syncMap) { if (!syncMap.containsKey("key")) { syncMap.put("key", 1); } } - 迭代必须在同步块内完成:
synchronized (syncMap) { for (Entry<string integer> e : syncMap.entrySet()) { ... } }</string>,否则可能抛ConcurrentModificationException
和 ConcurrentHashMap 的关键区别在哪
Collections.synchronizedMap() 是粗粒度锁(整个 map 一把锁),而 ConcurrentHashMap 是分段锁或 CAS + synchronized 细粒度控制,吞吐量高得多。尤其在读多写少、或存在大量并发迭代的场景下,前者容易成为瓶颈。
- 读操作也阻塞其他写操作,
ConcurrentHashMap的get()默认无锁 -
ConcurrentHashMap提供了原子方法如computeIfAbsent(),天然支持“查+存”逻辑,无需手写同步块 - 如果业务只需要线程安全的简单键值存取,且并发压力不大,
synchronizedMap足够;但一旦涉及迭代、批量操作或高并发,优先选ConcurrentHashMap
容易被忽略的初始化陷阱
传给 Collections.synchronizedMap() 的底层 map 如果本身已非空,包装后不会自动同步其已有状态——但这通常不是问题;真正踩坑的是:误把未同步的原始 map 引用暴露出去。
- 错误写法:
Map<string integer> raw = new HashMap(); Map<string integer> sync = Collections.synchronizedMap(raw); // 外部还能直接调用 raw.put(...) → 破坏线程安全性 </string></string>
- 正确做法:确保原始 map 仅作为构造参数,不保留引用;或用匿名内部类/工厂封装,避免泄漏
- 更稳妥的初始化:
Map<string integer> syncMap = Collections.synchronizedMap(new HashMap<string integer>() {{ put("a", 1); }});</string></string>(注意双大括号初始化的副作用,生产环境慎用)
Collections.synchronizedMap() 就形同虚设。










