跳转至

ConcurrentHashMap、HashMap、Hashtable 三者区别?

结论速查表

维度 HashMap Hashtable ConcurrentHashMap
线程安全 ✅(方法级 synchronized) ✅(分段锁 / CAS + synchronized)
性能 最高 最差(全表锁) 高(JDK 8 后)
null key / value ✅ 允许 ❌ 抛 NPE ❌ 不允许
初始容量 16 11 16
扩容倍数 2x 2x + 1 2x
迭代器 fail-fast fail-fast + enumerator 弱一致(weakly consistent)
底层结构 数组+链表+红黑树(JDK8) 数组+链表 数组+链表+红黑树 + CAS

详细对比

1. HashMap

  • 非线程安全,多线程下扩容可能成环形链表(JDK7 死循环)或数据丢失。
  • 允许一个 null key、多个 null value。
  • JDK8 后:数组 + 链表 + 红黑树(链表长度 ≥ 8 且数组长度 ≥ 64 树化)。

2. Hashtable

  • 遗留类,几乎所有 public 方法都加 synchronized,锁的是整个 table 对象。
  • 多线程下所有操作串行,性能极差。
  • 不允许 null key/value,否则 NPE。
  • 现在基本被 ConcurrentHashMap 取代。

3. ConcurrentHashMap

  • JDK7:分段锁 Segment[],每个 Segment 是一个小 Hashtable,并发度 = Segment 数(默认 16)。
  • JDK8:废弃 Segment,改为 数组 + 链表 + 红黑树,用 CAS + synchronized 锁单个桶头节点,并发度 = 桶数。
// JDK8 put 片段
if (f == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;                       // CAS 成功直接插入
}
synchronized (f) {                   // 锁桶头
    // 链表/红黑树插入
}

怎么选

  • 单线程或方法内局部变量:用 HashMap
  • 多线程共享 Map:用 ConcurrentHashMap不要用 Hashtable
  • 需要排序:TreeMap;需要并发排序:ConcurrentSkipListMap

常见误区

  • Collections.synchronizedMap(new HashMap<>()) 只是把每个方法包一层 synchronized,性能比 ConcurrentHashMap 差。
  • ConcurrentHashMap 的迭代器是弱一致的,不会抛 ConcurrentModificationException,也不保证看到迭代过程中的修改。