CHM(ConcurrentHashMap)中的 sizeCtl 的作用与值变化详解

学海无涯,志当存远。燃心砺志,奋进不辍。愿诸君得此鸡汤,如沐春风,学业有成。若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌

ConcurrentHashMap常简写为CHM,尤其是在讨论并发编程时。
在ConcurrentHashMap的源码中,有一个名为`sizeCtl`的关键字段,用于控制表的初始化和扩容。ConcurrentHashMap中sizeCtl的作用,包括其不同的取值状态
(如负数表示初始化或扩容中,正数表示初始容量等),以及它在并发控制中的具体应用。1. sizeCtl的作用,为什么需要它?
2. 值在不同阶段的变化,比如初始化时如何从0到-1,再变为阈值;扩容时如何变成负数,线程数如何影响这个值。
3. 源码中的具体操作,比如CAS的使用,如何确保线程安全。
4. 可能的值变化流程图或状态转换图,帮助用户直观理解。
5. 和类似机制的对比,比如与HashMap的不同之处,突出ConcurrentHashMap的并发优化。
6.sizeCtl在扩容完成后的更新过程,或者初始化失败后的处理。实际应用中:性能调优或调试时,sizeCtl的值变化异常,需要解释常见问题及解决方法。
ConcurrentHashMap内部机制以优化性能.

前言

sizeCtl 是 Java 并发编程中一个关键但容易混淆的概念。以下是详细解释:

sizeCtl 是 ConcurrentHashMap内部用于协调并发操作的核心状态控制变量,用于管理哈希表的初始化和扩容。它是一个 volatile int 类型的变量,通过 CAS(Compare and Swap)操作保证线程安全(无锁化)


一、sizeCtl 的核心作用

  1. 控制哈希表的初始化

    • 确保只有一个线程执行哈希表(Node[] table)的初始化。

    • 通过 CAS 将 sizeCtl 标记为 -1,阻止其他线程重复初始化

  2. 管理扩容操作

    • 触发扩容(当元素数量超过阈值时)。

    • 记录当前参与扩容的线程数量(通过负数表示)。

    • 协调多线程协作扩容(如协助迁移桶数据)。

  3. 存储容量阈值

    • 在未初始化时,存储用户指定的初始容量

    • 初始化完成后,存储扩容阈值(容量 * 负载因子,默认为 0.75)


二、sizeCtl 的取值含义

值范围含义
-1哈希表正在 初始化(仅允许一个线程操作)。
<-1哈希表正在 扩容,值为 -(1 + 扩容线程数)。例如 -2 表示有 1 个线程在扩容。
0默认初始状态,表示哈希表尚未初始化。
>0若表未初始化,表示用户指定的 初始容量
若已初始化,表示当前扩容阈值。

三、sizeCtl 的值变化流程

1. 初始化阶段
  • 初始状态sizeCtl = 0(默认值)。

  • 触发条件:首次插入元素时,若 table == null

  • 变化流程

    1.线程尝试通过 CAS 将 sizeCtl 从 0 改为 -1

    2.若 CAS 成功,当前线程执行初始化,其他线程自旋等待。

    3.初始化完成后,计算阈值(如 初始容量 * 0.75),设置 sizeCtl = 阈值

2. 扩容阶段
  • 触发条件:元素数量超过 sizeCtl 的值(当前阈值)。

  • 变化流程

    1.主导扩容的线程将 sizeCtl 更新为 -(1 + 扩容线程数)。例如,第一个线程设置 sizeCtl = -2

    2.其他线程检测到 sizeCtl < 0 时,可能协助扩容(增加扩容线程数,如 sizeCtl -= 1)。

    3.扩容完成后,计算新阈值(新容量 * 0.75),设置 sizeCtl = 新阈值

3. 动态调整示例
初始状态 → sizeCtl = 0
初始化 → sizeCtl = -1 → 初始化完成 → sizeCtl = 12(初始容量16,阈值12)
触发扩容 → sizeCtl = -2 → 其他线程协助 → sizeCtl = -3 → 扩容完成 → sizeCtl = 24(新容量32,阈值24)

四、源码关键逻辑解析

1. 初始化逻辑
// 源码片段(JDK 8+)
private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0) {// 其他线程正在初始化,当前线程让步Thread.yield();} else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// CAS 成功,当前线程执行初始化try {// 分配初始容量,设置阈值 sc = n - (n >>> 2)sizeCtl = sc;} finally {// 完成初始化}break;}}return tab;
}
2. 扩容逻辑
// 扩容触发点(addCount() 方法)
private final void addCount(long x, int check) {// ... 省略其他逻辑while (s >= (long)(sc = sizeCtl) && (tab = table) != null) {if (sc < 0) {// 协助扩容:更新 sizeCtl 的线程数if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) {transfer(tab, nextTab); // 数据迁移break;}} else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) {// 主导扩容:设置 sizeCtl = -2transfer(tab, null);break;}}
}

五、关键设计思想

  1. 无锁化并发控制
    通过 CAS 和自旋代替锁,减少线程阻塞,提升吞吐量。

  2. 状态与容量复用
    用 sizeCtl 一个变量同时表示状态(初始化、扩容)和容量阈值,减少内存占用。

  3. 多线程协作扩容
    允许多个线程同时迁移不同区间的桶数据,加速扩容过程


六、常见问题解答

  1. 为什么扩容时 sizeCtl 是负数?
    负数的高位为 1,通过符号区分状态(扩容/初始化)和正数容量,避免引入额外字段。

  2. 如何防止重复初始化或扩容?
    所有操作基于 CAS 原子性检查只有成功修改 sizeCtl 的线程才能执行操作

  3. 扩容完成后如何更新阈值?
    扩容完成后,根据新容量计算阈值(新容量 * 负载因子),并更新到 sizeCtl

  4. 默认阈值是多少?
    默认初始容量为 16,阈值为 12(16 * 0.75)

  5. 如何保证扩容安全?
    通过 sizeCtl 的 CAS 操作和扩容线程数标记,确保多线程协作的一致性。


七、总结

sizeCtl 是 ConcurrentHashMap 实现高效并发操作的核心机制:

  • 状态管理:统一控制初始化、扩容、阈值存储。

  • 线程协作:通过 CAS 和负数标记协调多线程工作。

  • 性能优化避免全局锁,分散竞争热点

理解 sizeCtl 的行为对调试高并发场景下的哈希表问题(如 初始化冲突、扩容卡顿)至关重要。实际开发中可通过监控 sizeCtl 的值变化,分析系统并发负载状态

八、额外学习之 初始化冲突

1. 问题场景

当多个线程首次调用 put 方法插入数据时,发现哈希表 table 未初始化,会触发并发初始化竞争。

2. 源码逻辑(initTable 方法)
private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0) {        // sizeCtl < 0 表示其他线程正在初始化Thread.yield();               // 当前线程让步(避免CPU空转)// CAS 抢占初始化权} else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try {// 执行初始化逻辑(分配 table 数组)table = new Node[sc];     // sc 初始为用户设置的容量sizeCtl = (int)(sc * 0.75); // 更新为扩容阈值} finally {// 初始化完成}break;}}return tab;
}
3. 冲突解决机制
  • CAS 原子操作:只有第一个线程能成功将 sizeCtl 从 0 或正数改为 -1,其他线程在 while 循环中检测到 sizeCtl < 0 时,通过 Thread.yield() 暂时让出 CPU。

  • 自旋等待:其他线程在 while 循环中不断检查 table 是否初始化完成,直到 table 不为空。

4. 问题案例

若初始化逻辑耗时较长(如复杂计算),可能导致其他线程长时间自旋等待,但 ConcurrentHashMap 的初始化操作(分配数组)本身是轻量级的,因此实际影响较小。

九、额外学习之 扩容卡顿

1. 问题场景

当哈希表元素数量超过阈值(sizeCtl)时,触发扩容(通常是翻倍)。若多个线程同时触发扩容或迁移数据,可能因资源竞争导致短暂卡顿

2. 源码逻辑(transfer 和 addCount 方法)
// addCount() 中触发扩容的逻辑
private final void addCount(long x, int check) {// ... 省略计数逻辑while (s >= (long)(sc = sizeCtl) && (tab = table) != null) {if (sc < 0) {  // 已有线程在扩容if ((rs = resizeStamp(tab.length)) == (sc >>> RESIZE_STAMP_SHIFT)) {// 协助扩容:CAS 增加扩容线程数if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {transfer(tab, nextTab); // 数据迁移break;}}} else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) {// 当前线程成为扩容主导者transfer(tab, null);break;}}
}// transfer() 中的分段迁移逻辑
void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;// 计算每个线程负责迁移的区间(stride)stride = (NCPU > 1) ? (n >>> 3) / NCPU : n;for (int i = 0; i < n; ++i) {// 迁移第 i 个桶的数据到新数组 nextTab}
}
3. 卡顿原因分析
  • 锁竞争:迁移桶数据时需要对原桶加锁(synchronized),若多个线程竞争同一桶锁,会导致等待。

  • 资源消耗:扩容涉及大量内存分配(新数组)和数据迁移(复制链表/树),占用 CPU 和内存带宽。

  • 线程协调开销:更新 sizeCtl 中的线程数需要频繁 CAS 操作。

4. 优化机制
  • 分段迁移:每个线程负责迁移不同区间的桶(stride 步长),减少锁竞争。

  • 多线程协作:通过 sizeCtl 记录扩容线程数,其他线程可协助迁移,加速扩容。

  • 渐进式扩容:迁移过程中,旧桶访问会触发协助迁移,避免集中式卡顿。


十、初始化冲突、扩容卡顿调试与诊断案例

1. 初始化冲突诊断
  • 现象:应用启动时大量线程卡在 initTable 的 while 循环中。

  • 日志分析
    通过 JVM 参数 -XX:+PrintCompilation 观察 initTable 方法的 JIT 编译情况,确认是否存在长时间自旋。

2. 扩容卡顿诊断
  • 现象:TPS 突然下降,响应时间飙升,伴随 transfer 方法栈堆积。

  • 排查工具

    • Arthaswatch ConcurrentHashMap transfer '{params, returnObj}' 监控迁移耗时。

    • JFR(JDK Flight Recorder):分析线程阻塞点和 CPU 占用。


设计总结

机制目标实现手段
无锁初始化避免全局锁竞争CAS 修改 sizeCtl + 自旋等待
协作式扩容分散迁移压力,加速扩容分段迁移(stride) + 多线程协助(CAS)
状态复用减少内存占用sizeCtl 同时表示状态和阈值
渐进式访问触发避免集中式迁移卡顿在读写操作中逐步触发迁移(helpTransfer

实际开发建议

  1. 避免伪共享
    CounterCell 和 Node 对象通过 @Contended 注解填充缓存行,减少伪共享(JDK 8+)。

  2. 合理设置初始容量

    new ConcurrentHashMap<>(initialCapacity);

    初始容量过小会导致频繁扩容,过大则浪费内存。

  3. 监控扩容阈值
    通过反射获取 sizeCtl 值,实时监控扩容状态:

    Field sizeCtlField = ConcurrentHashMap.class.getDeclaredField("sizeCtl");
    sizeCtlField.setAccessible(true);
    int sizeCtl = (int) sizeCtlField.get(map);

总结

ConcurrentHashMap 通过精细的状态控制(sizeCtl)和协作式并发设计,解决了初始化冲突和扩容卡顿问题。理解其源码机制,有助于在高并发场景下优化性能,并快速诊断潜在瓶颈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/73041.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VLAN综合实验报告

一、实验拓扑 网络拓扑结构包括三台交换机&#xff08;LSW1、LSW2、LSW3&#xff09;、一台路由器&#xff08;AR1&#xff09;以及六台PC&#xff08;PC1-PC6&#xff09;。交换机之间通过Trunk链路相连&#xff0c;交换机与PC、路由器通过Access或Hybrid链路连接。 二、实验…

OpenGL ES ->计算多个帧缓冲对象(Frame Buffer Object)+叠加多个滤镜作用后的Bitmap

XML文件 <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"><…

Java线程池深度解析:从使用到调优

适合人群&#xff1a;Java中级开发者 | 并发编程入门者 | 系统调优实践者 目录 一、引言&#xff1a;为什么线程池是Java并发的核心&#xff1f; 二、线程池核心知识点详解 1. 线程池核心参数与原理 2. 线程池的创建与使用 (1) 基础用法示例 (2) 内置线程池的隐患 3. 线…

【工具变量】全国地级市地方ZF债务数据集(2014-2023年)

地方ZF债务是地方财政运作的重要组成部分&#xff0c;主要用于基础设施建设、公共服务及经济发展&#xff0c;是衡量地方财政健康状况的重要指标。近年来&#xff0c;我国地级市的地方ZF债务规模不断变化&#xff0c;涉及一般债务和专项债务等多个方面&#xff0c;对金融市场、…

大模型训练的调参与算力调度技术分析

大模型训练的调参与算力调度 虽然从网络上&#xff0c;还有通过和大模型交流&#xff0c;了解了很多训练和微调的技术。但没有实践&#xff0c;也没有什么机会实践。因为大模型训练门槛还是挺高的&#xff0c;想要有一手资料比较困难。如果需要多机多卡&#xff0c;硬件成本小…

深入理解 lt; 和 gt;:HTML 实体转义的核心指南!!!

&#x1f6e1;️ 深入理解 < 和 >&#xff1a;HTML 实体转义的核心指南 &#x1f6e1;️ 在编程和文档编写中&#xff0c;< 和 > 符号无处不在&#xff0c;但它们也是引发语法错误、安全漏洞和渲染混乱的头号元凶&#xff01;&#x1f525; 本文将聚焦 <&#…

GRS认证的注意事项!GRS认证的定义

GRS认证的注意事项&#xff0c;对于企业而言&#xff0c;是通往可持续发展和环保生产道路上的重要里程碑。在追求这一认证的过程中&#xff0c;企业必须细致入微&#xff0c;确保每一个环节都符合严格的标准与要求。 首先&#xff0c;企业必须全面理解GRS认证的核心原则&#…

位运算--求二进制中1的个数

位运算–求二进制中1的个数 给定一个长度为 n 的数列&#xff0c;请你求出数列中每个数的二进制表示中 1 的个数。 输入格式 第一行包含整数 n。 第二行包含 n 个整数&#xff0c;表示整个数列。 输出格式 共一行&#xff0c;包含 n 个整数&#xff0c;其中的第 i 个数表…

Linux常用指令(3)

大家好,今天我们继续来介绍一下linux常用指令的语法,加深对linux操作系统的了解,话不多说,来看. 1.rmdir指令 功能&#xff1a;删除空目录 基本语法&#xff1a; rmdir 要删除的空目录 ⭐️rmdir删除的是空目录,如果目录下有内容是无法删除 2.mkdir指令 功能&#xff1a;创…

《Linux 网络架构:基于 TCP 协议的多人聊天系统搭建详解》

一、系统概述 本系统是一个基于 TCP 协议的多人聊天系统&#xff0c;由一个服务器和多个客户端组成。客户端可以连接到服务器&#xff0c;向服务器发送消息&#xff0c;服务器接收到消息后将其转发给其他客户端&#xff0c;实现多人之间的实时聊天。系统使用 C 语言编写&#x…

JavaIO流的使用和修饰器模式(直击心灵版)

系列文章目录 JavaIO流的使用和修饰器模式 文章目录 系列文章目录前言一、字节流&#xff1a; 1.FileInputStream(读取文件)2.FileOutputStream(写入文件) 二、字符流&#xff1a; 1..基础字符流:2.处理流&#xff1a;3.对象处理流&#xff1a;4.转换流&#xff1a; 三、修饰器…

【设计模式】SOLID 设计原则概述

SOLID 是面向对象设计中的五大原则&#xff0c;不管什么面向对象的语言&#xff0c; 这个准则都很重要&#xff0c;如果你没听说过&#xff0c;赶紧先学一下。它可以提高代码的可维护性、可扩展性和可读性&#xff0c;使代码更加健壮、易于测试和扩展。SOLID 代表以下五个设计原…

可发1区的超级创新思路:基于注意力机制的DSD-CNN时间序列预测模型(功率预测、交通流量预测、故障检测)

首先声明,该模型为原创!原创!原创! 一、应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、电机故障检测等等 二、模型整体介绍(本文以光伏功率预测为例) DSD-CNN(Depthwise-Spacewise Separable CNN)结合通道注意力机制,通过以下创新提升…

wsl2配置xv6全解(包括22.04Jammy)

文章目录 获取xv6源代码Ubuntu20.04 Version安装指令成功测试参考MIT2021年官方文档 24.04 Version安装指令成功测试参考MIT2024年官方文档 Ubuntu 22.04没有官方文档&#xff1f; 配置大体流程1. 卸载原本qemu&#xff08;如果之前安装了&#xff09;2. clone qemu官方源代码&…

招聘面试季--一文顿悟,Java中字节流和字符流的区别及使用场景上的差异

‌一、核心区别‌ ‌特性‌‌字节流‌‌字符流‌‌数据单位‌以字节&#xff08;8-bit&#xff09;为单位处理数据&#xff08;如0xA1&#xff09;以字符&#xff08;16-bit Unicode&#xff09;为单位处理数据&#xff08;如A, 你&#xff09;‌基类‌InputStream / OutputSt…

车载以太网网络测试-16【传输层-UDP】

目录 1 摘要2 车载以太网传输层概述3 车载以太网UDP协议3.1 车载以太网UDP协议的作用3.2 UDP报文帧结构3.3 UDP协议的通信过程3.3.1 通信过程3.3.2 实例示例3.3.3 代码示例 4 总结 1 摘要 车载以太网的第五层是传输层&#xff0c;它在车载网络架构中扮演着至关重要的角色。主要…

深度强化学习中的深度神经网络优化策略:挑战与解决方案

I. 引言 深度强化学习&#xff08;Deep Reinforcement Learning&#xff0c;DRL&#xff09;结合了强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;和深度学习&#xff08;Deep Learning&#xff09;的优点&#xff0c;使得智能体能够在复杂的环境中学…

无人机点对点技术要点分析!

一、技术架构 1. 网络拓扑 Ad-hoc网络&#xff1a;无人机动态组建自组织网络&#xff0c;节点自主协商路由&#xff0c;无需依赖地面基站。 混合架构&#xff1a;部分场景结合中心节点&#xff08;如指挥站&#xff09;与P2P网络&#xff0c;兼顾集中调度与分布式协同。 2.…

MQ,RabbitMQ,MQ的好处,RabbitMQ的原理和核心组件,工作模式

1.MQ MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中 保存消息的容器。它是应用程序和应用程序之间的通信方法 1.1 为什么使用MQ 在项目中&#xff0c;可将一些无需即时返回且耗时的操作提取出来&#xff0c;进行异步处理&#xff0…

django怎么配置404和500

在 Django 中&#xff0c;配置 404 和 500 错误页面需要以下步骤&#xff1a; 1. 创建自定义错误页面模板 首先&#xff0c;创建两个模板文件&#xff0c;分别用于 404 和 500 错误页面。假设你的模板目录是 templates/。 404 页面模板 创建文件 templates/404.html&#x…