外观
HashMap 和 CocurrentHashMap 的区别
⭐ 题目日期:
字节 - 2024/12/16
📝 题解:
HashMap 和 ConcurrentHashMap
是 Java 中两种常用的键值对集合,但它们在设计目标、线程安全性和实现细节上有显著区别。以下是两者的核心对比:
1. 线程安全性
特性 | HashMap | ConcurrentHashMap |
---|---|---|
线程安全 | ❌ 非线程安全 | ✅ 线程安全 |
同步机制 | 无 | 分段锁(JDK7)或 CAS + synchronized(JDK8+) |
多线程场景 | 需外部同步(如 Collections.synchronizedMap ) | 直接支持并发操作 |
示例问题: 多线程同时调用 HashMap.put()
可能导致数据丢失、死循环(JDK7 扩容时链表环形化)或 ConcurrentModificationException
。
2. 实现原理
特性 | HashMap | ConcurrentHashMap |
---|---|---|
数据结构 | 数组 + 链表/红黑树 | 类似 HashMap,但分段优化并发访问 |
锁粒度 | 无锁(线程不安全) | JDK7: 分段锁(锁一段桶) JDK8+: 锁单个桶(Node) |
哈希冲突处理 | 链表 → 红黑树 | 同 HashMap,但操作线程安全 |
JDK8+ 的优化:
ConcurrentHashMap
使用CAS
(无锁算法)和synchronized
仅锁定当前操作的桶(Node),而非整个表,极大减少锁竞争。
3. 性能对比
场景 | HashMap | ConcurrentHashMap |
---|---|---|
单线程 | ✅ 更高(无锁开销) | ⚠️ 略低(CAS 和锁检查开销) |
高并发读 | ❌ 不安全 | ✅ 高吞吐(读操作无锁) |
高并发写 | ❌ 不安全 | ✅ 优于 Hashtable (细粒度锁) |
原因:
ConcurrentHashMap
的读操作完全无锁(依赖volatile
可见性)。- 写操作仅锁定单个桶(JDK8+),而
Hashtable
锁定整个表。
4. 特殊行为
特性 | HashMap | ConcurrentHashMap |
---|---|---|
允许 null 键/值 | ✅ 允许 | ❌ 不允许(会抛 NullPointerException ) |
迭代器行为 | 快速失败(fail-fast ) | 弱一致性(weakly consistent ) |
示例:
HashMap
在迭代时修改会触发ConcurrentModificationException
。ConcurrentHashMap
的迭代器反映创建时的状态,但允许后续修改。
5. 适用场景
场景 | 推荐选择 |
---|---|
单线程环境 | HashMap |
高并发读多写少 | ConcurrentHashMap |
高并发写多 | ConcurrentHashMap |
需要 null 值 | HashMap (注意线程安全) |
6. 底层代码片段对比(JDK8+)
HashMap 的 put 方法(非线程安全):
final V putVal(int hash, K key, V value, boolean onlyIfAbsent) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); // 无锁直接插入
else {
// 处理哈希冲突...
}
// 检查扩容...
}
ConcurrentHashMap 的 put 方法(线程安全):
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) // 使用 CAS 无锁插入
break;
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
synchronized (f) { // 锁定当前桶
// 处理链表/红黑树插入...
}
}
}
// 检查树化...
}
总结
- HashMap:简单高效,适用于单线程或需手动控制同步的场景。
- ConcurrentHashMap:
- 高并发场景的首选,通过细粒度锁和 CAS 实现高性能线程安全。
- 设计上避免全局锁,显著提升并发吞吐量。
- 牺牲少量单线程性能,换取多线程安全性和扩展性。