ConcurrentHashMap 是 Java 提供的一种高效、线程安全的哈希表实现,主要用于多线程环境下替代传统的 HashMapHashtable


1. 基本概念

  • 所在包:java.util.concurrent
  • 线程安全: 是(高效)
  • 是否支持 null 键/值: 不支持(会抛出 NullPointerException
  • JDK 1.8 中采用锁分离 + CAS + 红黑树优化方案

2. 数据结构

JDK 1.8 中 ConcurrentHashMap 的底层结构如下:

transient volatile Node<K,V>[] table;
  • 底层是一个哈希数组,数组中的每个位置是一个链表或红黑树
  • 每个桶位的节点类型为 Node<K,V>TreeNode<K,V>(红黑树节点);
  • 节点结构:
static class Node<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}

3. 线程安全机制

核心特性:分段锁 + CAS + synchronized 组合

操作 机制说明
put(无冲突) CAS(无锁)
put(冲突) synchronized 锁定链表或树头节点
get 无锁(只读 volatile)
扩容 synchronized + 多线程协作
红黑树转换 synchronized + TreeBin 封装

详细锁机制说明:

1. CAS 操作(乐观锁)
  • 用于插入首节点、创建 table 等;
  • 通过 Unsafe.compareAndSwapObject 保证原子性;
  • 高性能,失败时重试。
2. synchronized 锁节点
  • 当 CAS 无法完成时(如链表存在),对桶头节点加锁
  • 范围非常小,仅锁当前链表,称为“细粒度锁”。
3. volatile 可见性
  • 所有关键字段都用 volatile 修饰,确保多线程间可见性。

4. put 操作流程

步骤简述:

1. 如果 table 未初始化,初始化 table(CAS)
2. 计算 key 的 hash,定位 bucket
3. 如果 bucket 为空,使用 CAS 插入
4. 如果不为空,使用 synchronized 加锁链表或红黑树头节点:
   - 查找是否存在 key,若存在更新
   - 若不存在,插入新节点
5. 判断链表长度是否超过阈值(8),转换为红黑树
6. 判断当前元素数量是否超过负载因子,触发扩容

5. 红黑树支持

  • 链表长度超过 8,且数组长度 ≥ 64 ⇒ 转为红黑树
  • 优点:避免链表退化为 O(n)
  • 使用 TreeBin 管理红黑树(不是直接用 TreeMap

6. 扩容机制

与 HashMap 不同:

  • HashMap:单线程扩容,可能死循环(JDK1.7);
  • ConcurrentHashMap:多线程协作扩容,无锁数据迁移。

关键点:

  • transfer() 方法使用 ForwardingNode 作为占位标记,避免并发冲突;
  • 线程在不同 bucket 上迁移,不重复工作;
  • 保证并发场景下的稳定性和安全性。

7. get 操作流程(无锁)

  1. 计算 hash 定位 bucket;
  2. 顺序遍历链表或红黑树;
  3. 比较 key 值,返回 value;
  4. 所有节点使用 volatile,保证可见性。

8. 常用方法说明

方法 是否线程安全 说明
get(K key) 无锁读取
put(K key, V value) CAS + synchronized 组合
remove(K key) 加锁删除
computeIfAbsent() 函数式接口并发安全支持
forEach() 是(弱一致性) 遍历过程支持并发修改

9. ConcurrentHashMap vs HashMap vs Hashtable

特性 HashMap Hashtable ConcurrentHashMap
线程安全 是(全方法加锁) 是(分段+CAS+synchronized)
性能 高(单线程) 低(锁粒度粗) 高(并发环境)
null 支持 ✅ 允许 null键和值 ❌ 不允许 ❌ 不允许
遍历时安全 ❌ 结构变会抛异常 ❌ 不 fail-fast ✅ 弱一致性
使用推荐场景 单线程场景 已淘汰 并发读写(推荐)

10. 注意事项和建议

  • 不支持 null 作为 key 或 value;
  • 遍历期间可以安全地并发修改(弱一致性);
  • 不保证元素顺序;
  • 适用于高并发缓存、共享数据容器;
  • 如果要强一致遍历,建议使用 synchronized 或加快写入频率控制。

11. 总结

优点 说明
高效线程安全 分段锁+CAS+synchronized 保证效率与安全
支持高并发 多线程读写性能远高于 Hashtable
局部加锁,粒度细 每次只锁单个桶位,不影响整体性能
支持红黑树优化 高冲突场景避免链表退化,保持高查找性能
支持多线程扩容 扩容性能高,避免阻塞

12. JDK 1.7 vs JDK 1.8:ConcurrentHashMap 实现对比

对比维度 JDK 1.7 实现(老版) JDK 1.8 实现(现代)
底层结构 Segment 数组 + HashEntry[] Node[] 数组(无 Segment)
锁粒度 Segment 锁(分段锁,16 段默认) 桶位节点锁(更细,基于 synchronized)
锁类型 显式悲观锁(ReentrantLock) CAS + synchronized(乐观 + 悲观)
并发扩容 不支持,扩容时阻塞整个表 支持多线程协作扩容
红黑树优化 不支持,链表可能退化为 O(n) 支持链表转红黑树,保持查询高效
put/get 过程 需定位 Segment,再定位桶 直接定位数组桶,减少层级
初始化机制 使用 final Segment[] 延迟初始化 使用 CAS 创建 table,按需初始化
遍历一致性 弱一致性 弱一致性(更快)
null 支持 不支持 不支持
性能和扩展性 并发度受限于 Segment 数 性能更优,适用于更高并发

总结:

  • JDK 1.7:通过「Segment 分段锁」实现线程安全,锁粒度为段,缺点是扩容时性能差、冲突严重时链表长;
  • JDK 1.8:摒弃 Segment,采用「数组 + CAS + synchronized + 红黑树」架构,锁更细、效率更高、并发性能显著提升
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐