ConcurrentHashMap 概述

ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中最重要的并发容器之一,专为多线程环境设计,用于解决哈希表的线程安全问题,同时保持高效的并发性能。它的核心目标是在保证线程安全的前提下,尽可能减少锁竞争,支持高并发的读写操作。

为什么需要 ConcurrentHashMap

在 ConcurrentHashMap 出现之前,处理哈希表的线程安全有两种方案:

  1. Hashtable:通过 synchronized 修饰所有方法,对所有共享资源整体上锁,当一个线程访问该资源时,其它线程必须阻塞等待,并发性能差
  2. 内部通过包装 HashMap,使用一个全局锁(mutex)保证安全,同样是全表锁,

ConcurrentHashMap 的出现打破了全表锁,大大提升了并发效率

JDK 1.7 中的实现方式

核心采用 “分段锁(Segment)” 机制,这是一种将哈希表分割为多个独立段(Segment),每段单独加锁的设计

每个 Segment 数组就是一个小的哈希表,自身继承了 ReentrantLock,即每一个 Segment 就是一个哈希表,Segment 中的每一个元素可以当作一个链表头节点,当有重复元素映射到此链表中,那么就会存储到这个链表。

核心操作原理

  1. 定位 Segment:根据 key 的哈希值计算出对应的 Segment 索引,确定操作的 Segment 段
  2. 获取 Segment 锁:调用 Segment 的 lock()方法上锁
  3. 在当前 Segment 对应的子数组中执行插入逻辑
  4. 调用 unlock()方法释放锁资源
    不同 Segment 上的操作完全并行。例如,线程 A 操作 Segment[0],线程 B 操作 Segment[1],两者无需竞争锁,可同时执行。
1
2
读操作无需加锁,子链表的 value 被 volatile 修饰,一个线程修改后,其他线程能立即看到最新值

JDK 1.8 后的 ConcurrentHashMap

废弃了分段锁,采用了 CAS 无锁算法和 synchronized 来进行了重构,进一步提升了并发性能。

底层结构:数组(Node)+ 链表 + 红黑树

  • Node 数组:维护了一个哈希桶,存储键值对
  • 链表与红黑树:当哈希冲突导致链表长度超过阈值(默认 8),且数组容量 ≥ 64 时,链表会转为红黑树(提升查询效率,从 O (n) 到 O (logn))

核心操作原理

  1. 计算哈希与索引:根据 key 的哈希值计算数组索引
  2. 初始化数组:通过 CAS 原子操作初始化数组
  3. 无哈希冲突:直接通过 CAS 插入新节点
  4. 有哈希冲突:
    • 若桶内时链表,对链表的头节点加 synchronized 锁,遍历链表查找或插入节点;若插入后链表长度超过阈值,转为红黑树。
    • 若桶内是红黑树:对红黑树的根节点加 synchronized 锁,执行插入操作。
  5. 扩容检查:若元素数量超过阈值(负载因子 * 容量),触发并发扩容(多线程协同迁移数据)。

核心特性

  • 线程安全:通过锁机制(分段锁或节点锁)+ CAS 保证多线程读写安全,避免了 HashMap 并发修改时的死循环、数据丢失等问题。
  • 高效并发:细粒度锁设计减少锁竞争,读操作无锁,写操作仅锁定冲突节点,支持高并发场景