8.Collections.synchronizedMap 与 ConcurrentMap 的区别与适用场景
Collections.synchronizedMap 与 ConcurrentMap 的区别与适用场景
🚀 高频指数:★★★★☆
🎯 你将收获:Java中两种线程安全Map的核心原理、性能差异、使用场景与底层机制。
一、问题导入
💬 面试官:
- 你知道
Collections.synchronizedMap()和ConcurrentHashMap的区别吗?- 什么时候该用前者,什么时候该用后者?
很多开发者知道“一个用同步块,一个高并发”,但不清楚它们的本质差异。
这题表面是集合问题,实际上考察的是:锁粒度 + 并发机制 + 线程安全保证层级。
二、两者的基本定义
| 类型 | 来自包 | 出现版本 | 线程安全机制 |
|---|---|---|---|
| Collections.synchronizedMap() | java.util | JDK 1.2 | 外层整体加锁(synchronized) |
| ConcurrentHashMap | java.util.concurrent | JDK 1.5 | 分桶锁 + CAS + 局部 synchronized |
☕️ 口诀:“一个外包锁,一个分区锁。”
三、Collections.synchronizedMap 原理
Collections.synchronizedMap() 是通过装饰器模式包装普通的 Map,
在所有访问操作上加 synchronized,保证原子性。
核心实现:
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {return new SynchronizedMap<>(m);
}static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {private final Map<K,V> m;final Object mutex; // 共享锁对象public V get(Object key) {synchronized (mutex) { return m.get(key); }}public V put(K key, V value) {synchronized (mutex) { return m.put(key, value); }}
}
特点:
- 每次访问(get、put、remove)都加全局锁;
- 同一时间仅允许一个线程访问 Map;
- 线程安全但并发性能差;
- 迭代时需手动加锁,否则可能抛出异常。
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
synchronized (map) {for (String key : map.keySet()) { ... }
}
四、ConcurrentHashMap 原理
ConcurrentHashMap 内部通过 分段锁 + CAS + 局部 synchronized 实现高并发访问。
特点:
- 读操作无锁;
- 写操作仅锁定单个桶(Node链表或树);
- 多线程同时写入不同桶互不影响;
- 支持弱一致性遍历(不会抛异常)。
✅ 相比
synchronizedMap,ConcurrentHashMap性能更高,可并发写入。
五、并发特性对比
| 对比项 | Collections.synchronizedMap | ConcurrentHashMap |
|---|---|---|
| 线程安全 | ✅ | ✅ |
| 锁粒度 | 整个Map(全局锁) | 桶级锁(局部锁) |
| 读操作 | 加锁 | 无锁 |
| 写操作 | 加锁 | CAS + synchronized |
| 并发度 | 1(单线程访问) | 多线程(默认支持16+并发) |
| 迭代时修改 | 抛 ConcurrentModificationException | 弱一致性,安全 |
| 是否允许 null 键/值 | ✅ | ❌ |
| 性能表现 | 低并发时可接受 | 高并发性能优越 |
☕️ 口诀:“同步Map一锁到底,并发Map桶桶分离。”
六、源码差异(简化版)
synchronizedMap:
public V put(K key, V value) {synchronized (mutex) {return m.put(key, value);}
}
ConcurrentHashMap:
final V putVal(K key, V value, boolean onlyIfAbsent) {for (;;) {if (casTabAt(tab, i, null, new Node<>(hash, key, value, null)))break; // 无锁插入成功else synchronized (f) {// 局部锁写入}}
}
七、性能表现(结论)
| 并发场景 | 建议选择 | 原因 |
|---|---|---|
| 低并发,线程少 | ✅ synchronizedMap | 简单稳定 |
| 高频读写并发 | ✅ ConcurrentHashMap | 性能高 |
| 多线程缓存 | ✅ ConcurrentHashMap | 支持CAS与分段扩容 |
| 迭代读取 | ✅ ConcurrentHashMap | 支持弱一致性 |
⚙️ 实际工程中,
ConcurrentHashMap已几乎完全取代synchronizedMap。
八、项目实战建议
- 缓存、在线用户表、任务状态表:使用
ConcurrentHashMap。 - 同步块简化、低并发配置类 Map:可用
synchronizedMap。 - 迭代同步时,
Collections.synchronizedMap需外层synchronized (map)。 - 不要在高并发环境下使用
synchronizedMap做计数器或任务队列。
九、面试官追问清单
| 面试问题 | 答题要点 |
|---|---|
| 线程安全 Map 有哪些? | Hashtable、synchronizedMap、ConcurrentHashMap |
| Hashtable 和 synchronizedMap 区别? | Hashtable 内部方法自带 synchronized,前者是装饰器包装。 |
| 为什么 ConcurrentHashMap 性能高? | 分桶锁 + CAS 并发写入,无全局锁。 |
| get 操作是否加锁? | synchronizedMap 会加锁;ConcurrentHashMap 不加锁。 |
| 允许 null 吗? | synchronizedMap 允许,ConcurrentHashMap 不允许。 |
十、口诀记忆
☕️ “同步一锁全,分桶并发高;读锁卡顿多,CAS 提速跑。”
十一、小结
| 知识点 | 关键结论 |
|---|---|
| synchronizedMap | 全局锁,简单但性能差 |
| ConcurrentHashMap | 分桶锁 + CAS,读写并行 |
| 适用场景 | 前者低并发,后者高并发 |
| 锁粒度 | 整体 vs 局部 |
| 是否推荐 | 新项目中推荐 ConcurrentHashMap |
✅ 一句话总结:“synchronizedMap 是老派同步,ConcurrentHashMap 是现代并发。”
🔗 延伸阅读
- 上一篇:ConcurrentHashMap 全流程解析:从分段锁到 CAS 与 AQS
- 下一篇:fail-fast 机制与 ConcurrentModificationException 深度剖析
- 📘 私信关键词【并发MapPDF】获取详细对比表与源码注释。
