collections.synchronizedmap仅保证单个方法调用线程安全,复合操作(如containskey+put)和迭代需手动加锁syncmap;适用于低并发读多写少场景,不适用于高频更新或需原子复合操作的高并发场景。

Java 中的 Collections.synchronizedMap 是一种轻量、易用的线程安全封装方式,但它不是“开箱即用就完全安全”的万能解。它的核心作用是让单个操作(如 put、get)线程安全,但复合逻辑和迭代仍需开发者主动加锁。理解这一点,才能避免并发 bug。
它到底做了什么?——粗粒度全局锁
调用 Collections.synchronizedMap(new HashMap()) 后,返回的是一个 SynchronizedMap 包装器实例。它内部持有一个原始 Map(比如 HashMap),所有方法(put、get、size 等)都用 synchronized(mutex) 包裹,而默认锁对象就是该包装器自身。
- 每次调用都会独占整个 Map,同一时刻只允许一个线程执行任意操作
- 锁粒度大,高并发下容易成为瓶颈,吞吐量不如
ConcurrentHashMap - 不改变底层 Map 行为:比如 HashMap 的扩容、哈希冲突处理等照常发生,只是被串行化了
哪些操作是安全的?哪些不是?
单次原子方法调用是线程安全的;但多个操作组合或遍历,必须额外同步。
- ✅ 安全:
map.put("k", 1)、map.get("k")、map.containsKey("k") - ❌ 不安全:
if (!map.containsKey("k")) map.put("k", 1)—— 中间可能被其他线程插入,导致重复写入 - ❌ 不安全:直接用
for (String k : map.keySet())遍历 —— 迭代器本身未同步,可能抛ConcurrentModificationException
这类场景必须显式加锁:
synchronized (syncMap) {
if (!syncMap.containsKey("k")) {
syncMap.put("k", 1);
}
}
synchronized (syncMap) {
for (String key : syncMap.keySet()) {
System.out.println(key + " → " + syncMap.get(key));
}
}
和 ConcurrentHashMap 比,该怎么选?
二者定位不同,不是简单替代关系。
-
ConcurrentHashMap:分段锁(JDK 8+ 用 CAS + synchronized 细粒度桶锁),支持高并发读写,迭代也安全(弱一致性),适合中高并发场景 -
Collections.synchronizedMap:实现简单、内存开销小、语义明确(完全串行),适合低并发、逻辑简单、或已有 HashMap 代码想快速加固的场景 - 注意:
ConcurrentHashMap不允许null键/值,而synchronizedMap继承底层 Map 的限制(如 HashMap 允许 null 值)
使用时容易忽略的关键细节
几个实战中常踩的坑:
- 包装后的 Map 仍需保证底层 Map 不被外部直接访问 —— 否则绕过同步,破坏安全性
- 构造时可传入自定义锁对象:
Collections.synchronizedMap(map, lockObj),便于与其他操作共用同一把锁 -
keySet()、entrySet()、values()返回的集合也是同步的,但它们内部的迭代器仍需外层同步 - 不推荐用于高频写+复杂条件判断的场景,此时应优先重构为
ConcurrentHashMap.computeIfAbsent等原子方法
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











