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 方法的实现细节。

相关推荐
方也_arkling1 小时前
【Java-Day08】static / final / 枚举
java·开发语言
橙淮1 小时前
Spring Bean作用域与生命周期全解析
java·spring
Chengbei112 小时前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
llz_1122 小时前
web-第一次课后作业
java·开发语言·idea
秋92 小时前
Java项目运行5天左右自动宕机:系统性定位与解决方案
java·开发语言·python
小江的记录本2 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
DIY源码阁2 小时前
JavaSwing学生成绩管理系统 - MySQL版
java·数据库·mysql·eclipse
basketball6163 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
JAVA面经实录9174 小时前
MyBatis面试题库
java·mybatis
小江的记录本4 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试