当前位置: 首页 > news >正文

ConcurrentHashMap解析

ConcurrentHashMap解析

以下是对 Java 8 及以后版本 ConcurrentHashMap 源码的深入解析,涵盖其底层数据结构、并发控制机制、核心操作流程、扩容与迁移、树化/退化策略,以及性能特性。总体来说,ConcurrentHashMap 在 JDK 8 中摒弃了原有的 Segment 分段锁,采用了「数组 + 链表/红黑树 + CAS + synchronized」的设计,使得检索无锁、更新高并发,且在扩容时支持多线程协作,以此在确保线程安全的同时,最大化地提升性能和可伸缩性。

底层数据结构

ConcurrentHashMap 维护一个类型为 Node<K,V>[] table 的主数组,数组长度始终为 2 的幂次方,每个桶(bin)最初存储 Node 节点(单链表)或 TreeBin(红黑树)

  • Node:包含 final int hash; final K key; volatile V val; volatile Node<K,V> next;,用于链表结构的实现
  • TreeBin:当单链表长度 ≥ 8 且数组容量 ≥ 64 时,链表会转换为红黑树节点(TreeNode),以将单桶最坏查找从 O(n) 降至 O(log n)

并发控制机制

CAS 与 Unsafe

ConcurrentHashMap 使用 sun.misc.Unsafe 提供的 CAS 原语(casTabAtU.compareAndSwapObject 等)来在无锁情况下完成桶头的插入(桶中无数据时)与更新,从而实现高效的并发写

当 CAS 插入失败(即已存在元素,或链表/树操作),会对该桶首节点 **f** **synchronized(f)** 锁以串行化后续对链表或树结构的修改,保证冲突处理的正确性而不会全表加锁

核心操作流程

初始化
  • 构造 ConcurrentHashMap 实例时,table 并不立即分配;首个插入或显式调用 put 时,才通过 CAS 确保单线程完成初始化(容量默认为 16)
get
  • 直接读取 table(不加锁);
  • 计算扰动哈希 h = spread(key.hashCode()) 并定位桶索引 i = (n – 1) & h
  • tab[i] == null,返回 null;若首节点匹配则返回值;否则依次遍历链表或在 TreeBin 中调用 find 进行对数级查找
put
  1. 计算哈希并尝试在对应桶通过 CAS 插入新 Node 作为头节点;
  2. 若 CAS 失败且 tab[i] 已存在:
    • 对首节点 f 加锁,检查键是否已存在,存在则覆盖;
    • 否则将新节点追加到链表尾部,若链表长度达 8 且表容量 ≥ 64,则调用 treeifyBin 将桶转为红黑树;
  1. 更新计数器 size,如超过阈值 threshold = capacity * loadFactor,触发扩容
removecompute
  • remove 同样通过 CAS 快速尝试删除头节点,失败则锁定对应桶进行遍历删除;
  • computeIfAbsent 等高级方法在锁定桶头后进行 remappingFunction 调用,并在必要时插入新节点,递归调用时会触发 OpenJDK 中对递归的防护机制(过于严格时可参考 JDK-8294891 报告)

扩容与迁移

扩容由 transfer 方法完成,支持多线程协作:

  1. 单线程初始化 **nextTable**(容量翻倍)并设置 transferIndex
  2. 多线程迁移:各线程通过 CAS 递减 transferIndex 分配任务块,遇到 forwarding 标记则跳过,遍历旧 Node 并按高位拆分到新表的 ii + oldCap 位置;
  3. 迁移完成后,nextTable 替代 table,更新 sizeCtl 为新容量 × 0.75

树化与退化

  • 树化阈值:链表长度 ≥ 8 且表容量 ≥ 64 时由 treeifyBin 转为红黑树;
  • 退化阈值:在迁移过程中,若树节点数 < 6,则调用 untreeifyTreeBin 转回链表,以减少平衡维护开销

性能与特性

  • 并发级别:检索无锁(完全并发),更新高预期并发性(局部 synchronized + CAS)
  • 时间复杂度:平均 O(1),最坏 O(log n)(树化后);
  • 内存开销:红黑树节点需额外维护父/左右指针与颜色标志;
  • 线程安全:内部确保了多线程访问安全,无死锁风险,但不支持全表锁定;全表级别的操作需外部同步或使用其他并发集合。

CAS 和Synchronized的使用时机

1. CAS(Compare-And-Swap)使用场景 🔄

适用于无冲突低冲突操作,确保乐观并发性。
  • 表初始化

    • 使用 CAS 保证 table 初始化只由一个线程成功(initTable() 方法)。
    • casTabAt(tab, i, null, newNode) 用于无锁创建桶。
  • 节点插入(无冲突时)

    • 当目标桶 tab[i] 为空,直接使用 CAS 插入新节点,无需加锁。
  • 计数器更新

    • 使用 CAS 递增 baseCount 计数器(统计元素个数)。
    • 如果 CAS 失败,才会退化到 fullAddCount(),分散计数压力。
典型源码片段:
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) {break; // 成功插入
}

2. synchronized 使用场景 🔒

适用于存在冲突复杂结构变更的操作,确保悲观锁定。
  • 桶内链表/树操作

    • 如果 CAS 插入失败(表示桶非空),需要遍历链表或红黑树;
    • 此时,对桶头节点 synchronized (f) 加锁,串行化冲突修改。
  • 链表转红黑树(treeify):

    • 当桶中链表长度 ≥ 8,需要加锁完成链表到红黑树的结构变换。
  • 红黑树操作

    • TreeBin 内部操作,比如插入、删除、旋转、平衡时,用 synchronized 确保树结构正确。
  • 复杂删除和 compute 方法

    • remove()computeIfAbsent()compute(),涉及读取 + 修改;
    • 这些方法会对桶头 synchronized 来串行化整个修改逻辑。
典型源码片段:
synchronized (f) {Node<K,V>[] tab2;// 遍历链表/树进行插入或更新
}

✅ 总结记忆口诀:
场景用法
无冲突,简单插入CAS
冲突、遍历、结构变更synchronized
链表 → 红黑树转化synchronized
红黑树插入/删除synchronized
size 计数更新CAS + 分段计数

🚩设计哲学:
  • 优先使用 CAS(乐观并发) → 快速失败重试,效率高;
  • 结构性修改退化为 synchronized → 保证链表/树一致性,不追求无锁,但控制在桶级别,避免全表锁。

相关文章:

  • Java高频面试之并发编程-14
  • 设备管理系统深度测评:如何用 AI 知识库实现故障智能诊断?
  • 沃伦森电容器支路阻抗特性监控系统 电容器组智能健康管理专家
  • 模拟内存管理
  • 工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计 从架构到实操
  • 若依框架Ruoyi-vue整合图表Echarts中国地图标注动态数据
  • TCP/IP和OSI对比
  • 果汁厂通信革新利器:Ethernet/IP转CANopen协议网关
  • 网盘解析工具更新,支持UC网盘!!
  • 从艾米・阿尔文看 CTO 的多面特质与成长路径
  • 使用DEEPSEEK快速修改QT创建的GUI
  • LLM 论文精读(三)Demystifying Long Chain-of-Thought Reasoning in LLMs
  • 深度学习笔记41_调用Gensim库训练Word2Vec模型
  • 什么是:Word2Vec + 余弦相似度
  • 又双叒叕想盘一下systemd
  • Lua从字符串动态构建函数
  • Pandas比MySQL快?
  • 全国青少年信息素养大赛 Python编程挑战赛初赛 内部集训模拟试卷三及详细答案解析
  • 计算机科技笔记: 容错计算机设计01 概述 教材书籍 课程安排 发展历史
  • 不止于观赏:3D 全景漫游的多元价值​
  • 习近平同瑞典国王卡尔十六世·古斯塔夫就中瑞建交75周年互致贺电
  • 85后清华博士黄佐财任湖北咸宁市咸安区委副书记、代区长
  • 金融监管局:已设立74支私募股权投资基金,支持投资科技创新企业
  • 明天起,沪苏湖高铁、杭温高铁推出13款新型票制产品
  • 特色茶酒、非遗挂面……六安皋品入沪赴“五五购物节”
  • 湖南张家界警方公告宣布一名外国人居留许可作废