HashMap vs Hashtable vs ConcurrentHashMap
面试官问:"多线程环境下该用哪个 Map?"
候选人小李答:"ConcurrentHashMap。"
面试官追问:"为什么不用 Hashtable?它也是线程安全的。"
小李说:"Hashtable 太慢了。"
面试官继续追问:"那 Collections.synchronizedMap 呢?为什么不推荐用?"
小张答不上来了。
【面试官心理】 这道题我用来测试候选人对并发编程的理解深度。能说清楚三种 Map 的区别、性能差异、适用场景的候选人,说明真正理解了什么叫做"线程安全"和"并发性能"。
一、三种 Map 概述 🔴
1.1 HashMap
特点:
- 非线程安全
- O(1) 查找/插入
- 允许一个 null key
- JDK 1.2 引入
- 不支持并发操作
1.2 Hashtable
特点:
- 线程安全(每个方法都用 synchronized)
- O(1) 查找/插入
- 不允许 null key
- JDK 1.0 引入
- 已被废弃,不推荐使用
1.3 ConcurrentHashMap
特点:
- 线程安全(CAS + synchronized)
- O(1) 查找/插入
- 不允许 null key
- JDK 1.5 引入
- 推荐的多线程 Map
二、线程安全实现对比 🔴
2.1 Hashtable 的 synchronized
问题:所有操作都是全局锁,高并发下性能极差。
2.2 ConcurrentHashMap 的分段锁/桶锁
2.3 锁粒度对比
2.4 ❌ 错误示范
候选人原话:"Hashtable 和 ConcurrentHashMap 都是线程安全的,用哪个都一样。"
问题诊断:
- 不理解"线程安全"和"并发性能"的区别
- 不理解锁粒度对性能的影响
面试官内心 OS:"这个候选人可能只是知道 Hashtable 不能用了,但没有理解为什么。"
【面试官心理】 面试官追问"为什么 Hashtable 不推荐",是在测试候选人对并发性能的理解。能够解释清楚锁粒度、性能差异的候选人,说明真正理解了并发编程。
三、性能对比 🟡
3.1 单线程性能
3.2 多线程性能
3.3 性能对比表
四、null 支持差异 🟡
4.1 HashMap
4.2 Hashtable
4.3 ConcurrentHashMap
4.4 为什么 ConcurrentHashMap 不允许 null
ConcurrentHashMap 不允许 null 是为了避免并发场景下的歧义。如果需要表示"无值",用 Optional 或特殊值(如 -1、EMPTY)。
五、复合操作的线程安全 🟡
5.1 非原子操作的危险
5.2 原子操作方法
5.3 Collections.synchronizedMap
六、迭代器行为差异 🟡
6.1 HashMap
6.2 Hashtable
6.3 ConcurrentHashMap
6.4 弱一致迭代器的实现
七、选型决策树 🟡
7.1 何时用 HashMap
7.2 何时用 ConcurrentHashMap
7.3 何时用 Hashtable
7.4 选型决策图
八、Collections.synchronizedMap vs ConcurrentHashMap 🟢
8.1 性能对比
8.2 迭代对比
8.3 原子操作对比
永远不要用 Collections.synchronizedMap,它的问题和 Hashtable 一样(全局锁),但没有 Hashtable 稳定。ConcurrentHashMap 在任何场景下都比它更好。
九、生产避坑清单 🟡
9.1 ❌ 常见错误
9.2 并发场景最佳实践
9.3 HashMap 在多线程下的风险
十、面试高频追问 🟡
10.1 第一层追问
面试官:"Hashtable 为什么被废弃了?"
候选人:...
正确回答:
- 每个方法都用 synchronized 加全局锁
- 锁粒度太粗,高并发下性能极差
- ConcurrentHashMap 提供了更好的替代方案
10.2 第二层追问
面试官:"ConcurrentHashMap 和 Collections.synchronizedMap 有什么区别?"
候选人:...
正确回答:
- Collections.synchronizedMap:全局锁,和 Hashtable 一样
- ConcurrentHashMap:CAS + synchronized,锁粒度细
- 性能:ConcurrentHashMap > Collections.synchronizedMap
10.3 第三层追问
面试官:"ConcurrentHashMap 的 key/value 能为 null 吗?"
候选人:...
正确回答:不能。如果为 null,会抛出 NullPointerException。这是和 HashMap 的主要区别之一。原因是避免并发场景下"key 不存在"和"key 存在但 value 为 null"的歧义。
10.4 第四层追问
面试官:"ConcurrentHashMap 的迭代器是 fail-fast 吗?"
候选人:...
正确回答:不是。ConcurrentHashMap 的迭代器是弱一致的。迭代过程中允许其他线程修改,不会抛出 ConcurrentModificationException。
十一、三种 Map 总结对比
【学习小结】 三种 Map 核心要点:
- HashMap:单线程首选,性能最好,非线程安全
- Hashtable:已被废弃,全局锁,性能差
- ConcurrentHashMap:多线程首选,CAS + synchronized,支持原子操作
- 永远不要用 Collections.synchronizedMap,它的问题和 Hashtable 一样
- ConcurrentHashMap 不允许 null 是为了避免并发歧义