Java ConcurrentHashMap源码深度解析:从底层原理到性能优化

Java ConcurrentHashMap源码深度解析:从底层原理到性能优化

引言

ConcurrentHashMap 是 Java 并发编程中非常核心的类,它在保证线程安全的同时,提供了极高的并发性能。与 Hashtable 相比,ConcurrentHashMap 通过分段锁(Segment)或更先进的 CAS + Synchronized 机制,避免了全局锁带来的性能瓶颈。本文将从源码级别深入剖析 ConcurrentHashMap 的设计思想、数据结构、核心方法实现以及性能优化策略。


1. 历史演进:从 JDK 7 到 JDK 8

1.1 JDK 7: Segment 分段锁机制

JDK 7 中的 ConcurrentHashMap 采用 分段锁(Segment)的设计。其核心思想是将整个哈希表划分为多个段(Segment),每个段独立加锁,从而允许多个线程同时操作不同的段,提升并发能力。

  • 数据结构Segment<K,V>[] segments,每个 Segment 实际上是一个小型的 HashEntry 表。
  • 锁粒度:锁作用于 Segment,而非整个 Map。
  • 缺点:虽然提升了并发性,但依然存在锁竞争和扩容时的性能问题。

1.2 JDK 8: CAS + Synchronized + Node 数组 + 链表/红黑树结构

JDK 8 对 ConcurrentHashMap 进行了重大重构,摒弃了 Segment,引入了更高效的 CAS 操作 + synchronized 锁 + 红黑树 的组合。

  • 数据结构Node<K,V>[] table,数组 + 链表 + 红黑树。
  • 锁粒度 :锁作用于 桶(bucket),即链表或红黑树的头节点。
  • 核心优势:锁的粒度更细,极大减少了锁竞争,提升了高并发下的性能。

2. 核心数据结构解析(JDK 8)

2.1 Node 结构

java 复制代码
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
}
  • hash: 键的哈希值,用于定位桶位置。
  • key: 键,不可变。
  • val: 值,使用 volatile 保证可见性。
  • next: 指向下一个节点,构成链表。

2.2 TreeNode 与 TreeBin

当链表长度超过阈值(默认为 8),会转换为红黑树以提高查找效率。

java 复制代码
static final class TreeNode<K,V> extends Node<K,V> {
    // 红黑树节点结构,包含 parent, left, right 等指针
}

static final class TreeBin<K,V> {
    TreeNode<K,V> root;
    // 其他字段...
}

3. 核心方法源码分析(JDK 8)

3.1 put(K key, V value) - 插入操作

3.1.1 步骤分解
  1. 计算 hashhash = spread(key.hashCode()),防止哈希冲突。
  2. 判断是否需要初始化 :如果 table == null,调用 initTable() 进行初始化。
  3. 定位桶位置i = (n - 1) & hash,其中 n 是 table 的长度。
  4. CAS 检查空桶 :若桶为空,使用 casTabAt(table, i, null, new Node<>(hash, key, value, null)) 原子插入。
  5. 处理链表/红黑树:如果桶不为空,且是链表,则遍历链表;如果是红黑树,则调用红黑树插入方法。
  6. 同步锁 :对桶的头节点加 synchronized 锁,确保修改的原子性。
  7. 扩容检查 :如果插入后元素数量超过阈值,触发 transfer() 扩容。
3.1.2 CAS 与 Synchronized 的协同
  • CAS:用于无锁场景(如空桶插入)。
  • Synchronized:用于有竞争的场景(如链表插入),锁住的是头节点,而不是整个 map。

关键点:锁的粒度是「桶」,而非「整个 map」,这是性能提升的核心。

3.2 get(K key) - 读取操作

java 复制代码
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        if ((eh = e.hash) == h && ((ek = e.key) == key || (ek != null && ek.equals(key))))
            return e.val;
        while ((e = e.next) != null) {
            if (e.hash == h && ((ek = e.key) == key || (ek != null && ek.equals(key))))
                return e.val;
        }
    }
    return null;
}
  • 特点完全无锁 !读操作不加任何锁,仅依赖 volatile 保证可见性。
  • 优势:读操作性能极高,几乎不受并发影响。

3.3 resize() / transfer() - 扩容机制

  • 并发扩容 :不同于 HashMapConcurrentHashMap 支持并发扩容。
  • 工作方式 :多个线程可以共同参与扩容,将旧 table 中的元素迁移到新 table,通过 sizeCtl 变量协调。
  • 避免死锁 :使用 CAS 检查并设置 sizeCtl 来控制扩容状态。

4. 性能优化策略总结

优化点 说明
细粒度锁 锁作用于桶头节点,极大减少锁竞争
CAS 无锁操作 在无竞争时,通过 CAS 实现原子更新
读操作无锁 读取不加锁,利用 volatile 保证可见性
红黑树优化 链表过长时转为红黑树,降低查找时间复杂度
并发扩容 多线程协作完成扩容,避免阻塞

5. 使用建议与注意事项

  • 不要在遍历时修改集合ConcurrentHashMap 不支持在迭代时修改,否则会抛出 ConcurrentModificationException
  • 避免键或值为 null :虽然允许,但可能导致 get() 返回 null 无法区分是不存在还是值为 null。
  • 合理设置初始容量:可减少扩容次数,提升性能。
  • 优先使用 computeIfAbsent 等原子操作:避免手动同步代码块。

总结

ConcurrentHashMap 是 Java 并发编程中的典范之作。它通过 分段锁 → CAS+Synchronized 的演进,实现了高性能与线程安全的完美平衡。理解其源码不仅有助于我们写出更高效的并发代码,也为我们学习其他并发容器(如 ConcurrentSkipListMap)打下坚实基础。

推荐 :结合 JDK 8 源码阅读,重点关注 put, get, resize 方法的实现细节。

相关推荐
不愿是过客7 小时前
java实战干货——长方法深递归
java
小北方城市网8 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
六义义9 小时前
java基础十二
java·数据结构·算法
毕设源码-钟学长9 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
笨手笨脚の10 小时前
深入理解 Java 虚拟机-03 垃圾收集
java·jvm·垃圾回收·标记清除·标记复制·标记整理
莫问前路漫漫10 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔10 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
挖矿大亨10 小时前
c++中的函数模版
java·c++·算法
a程序小傲11 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
青春男大11 小时前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存