Skip to content

HashMap 和 CocurrentHashMap 的区别

约 788 字大约 3 分钟

Java基础字节

2025-03-17

⭐ 题目日期:

字节 - 2024/12/16

📝 题解:

HashMap 和 ConcurrentHashMap 是 Java 中两种常用的键值对集合,但它们在设计目标、线程安全性和实现细节上有显著区别。以下是两者的核心对比:


1. 线程安全性

特性HashMapConcurrentHashMap
线程安全❌ 非线程安全✅ 线程安全
同步机制分段锁(JDK7)或 CAS + synchronized(JDK8+)
多线程场景需外部同步(如 Collections.synchronizedMap直接支持并发操作

示例问题: 多线程同时调用 HashMap.put() 可能导致数据丢失、死循环(JDK7 扩容时链表环形化)或 ConcurrentModificationException


2. 实现原理

特性HashMapConcurrentHashMap
数据结构数组 + 链表/红黑树类似 HashMap,但分段优化并发访问
锁粒度无锁(线程不安全)JDK7: 分段锁(锁一段桶) JDK8+: 锁单个桶(Node)
哈希冲突处理链表 → 红黑树同 HashMap,但操作线程安全

JDK8+ 的优化

  • ConcurrentHashMap 使用 CAS(无锁算法)和 synchronized 仅锁定当前操作的桶(Node),而非整个表,极大减少锁竞争。

3. 性能对比

场景HashMapConcurrentHashMap
单线程✅ 更高(无锁开销)⚠️ 略低(CAS 和锁检查开销)
高并发读❌ 不安全✅ 高吞吐(读操作无锁)
高并发写❌ 不安全✅ 优于 Hashtable(细粒度锁)

原因

  • ConcurrentHashMap 的读操作完全无锁(依赖 volatile 可见性)。
  • 写操作仅锁定单个桶(JDK8+),而 Hashtable 锁定整个表。

4. 特殊行为

特性HashMapConcurrentHashMap
允许 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 实现高性能线程安全。
    • 设计上避免全局锁,显著提升并发吞吐量。
    • 牺牲少量单线程性能,换取多线程安全性和扩展性。