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

ConcurrentHashMap扩容

目录

一、tryPreSize方法-初始化数组

二、tryPreSize方法-扩容标识戳

三、transfer方法-构建新数组

四、transfer方法-迁移数据

五、transfer方法-lastRun机制

六、helpTransfer方法-协助扩容


三种触发方式

达到了扩容的阈值

一、tryPreSize方法-初始化数组

// 扩容前操作,putAll,链表转红黑树  插入map的长度(putAll)
private final void tryPresize(int size) {
    // 这个判断是给putAll留的,要计算当前数组的长度(初始化)
    // 如果size大于最大长度 / 2,直接将数组长度设置为最大值。
    // tableSizeFor,将长度设置的2的n次幂
    // c是初始化数组长度
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1);
    // sc是给sizeCtl赋值
    // -1:正在初始化数组,小于-1:正在扩容,0:代表还没初始化数组,大于0:可能初始化了(代表阈值),也可能没初始化(初始化的长度)
    int sc;
    while ((sc = sizeCtl) >= 0) {
        // 代表没有正在执行初始化,也没有正在执行扩容。、
        // tab:数组,n:数组长度
        Node<K,V>[] tab = table; int n;
        // 判断数组是不是还没初始化呢
        if (tab == null || (n = tab.length) == 0) {
            // 初始化数组,和initTable一样的东西
            // 在sc和c之间选择最大值,作为数组的初始化长度
            n = (sc > c) ? sc : c;
            // 要初始化,就直接把sizeCtl设置为-1,代表我要初始化数组
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    // DCL!
                    if (table == tab) {
                        // 创建数组
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        // 初始化数组赋值给成员变量
                        table = nt;
                        // sc先设置成阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // 将sc赋值给sizeCtl
                    sizeCtl = sc;
                }
            }
        }
        // 要么是c没有超过阈值,要么是超过最大值,啥事不做~~~
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        // 省略部分代码。
    }
}

二、tryPreSize方法-扩容标识戳

// 扩容前操作
private final void tryPresize(int size) {
    while ((sc = sizeCtl) >= 0) {
        // 省略部分初始化代码
        Node<K,V>[] tab = table; int n;
        if (tab == null || (n = tab.length) == 0) {
        // 扩容前操作!
        else if (tab == table) {
            // 计算扩容标识戳(基于老数组长度计算扩容标识戳,因为ConcurrentHashMap允许多线程迁移数据。)
            int rs = resizeStamp(n);
            // 这里是一个BUG,当前sc在while循环中,除了初始化没有额外赋值的前提下,这个sc < 0 永远进不来。
            // 虽然是BUG,但是清楚sc < 0 代表正在扩容
            if (sc < 0) {
                Node<K,V>[] nt;    31 ~ 16   15 ~ 0
                // 这里是第二个BUG
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs ||   // 判断协助扩容线程的标识戳是否一致
                    sc == rs << RESIZE_STAMP_SHIFT + 1 ||    // BUG之一,在判断扩容操作是否已经到了最后的检查阶段
                    sc == rs << RESIZE_STAMP_SHIFT + MAX_RESIZERS ||   // BUG之一,判断扩容线程是否已经达到最大值
                    (nt = nextTable) == null ||  // 新数组为null,说明也已经扩容完毕,扩容完毕后,才会把nextTable置位null
                    transferIndex <= 0) // transferIndex为线程领取任务的最大节点,如果为0,代表所有老数据迁移任务都没领干净了
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 还没有执行扩容,当前线程可能是第一个进来执行扩容的线程
            // 基于CAS的方式,将sizeCtl从原值改为 扩容标识戳左移16位
            // 10000000 00011010 00000000 00000010  一定是< -1的负数,可以代表当前ConcurrentHashMap正在扩容
            // 为什么是低位+2,代表1个线程扩容。 低位为5,就代表4个线程正在并发扩容
            // 扩容分为2部:创建新数组,迁移数据。
            // 当最后一个线程迁移完毕数据后,对低位-1.最终结果低位还是1,需要对整个老数组再次检查,数据是否迁移干净
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_S

http://www.kler.cn/a/544578.html

相关文章:

  • Spring Boot 中的事务管理:默认配置、失效场景及集中配置
  • Android原生的HighCPU使用率查杀机制
  • 基于Python的医院运营数据可视化平台:设计、实现与应用(下)
  • 使用CherryStudio、Ollama、腾讯云搭建本地个人知识库、智能体
  • LabVIEW外腔二极管激光器稳频实验
  • 【leetcode】关于循环数组的深入分析
  • Opensearch/ElasticSearch-ctx查询内容不全的问题
  • Python从0到100(八十八):LSTM网络详细介绍及实战指南
  • 基于千兆5G网关的5G急救车方案
  • git用法(简易版)
  • SSH 代理与私钥持久化:让你的开发环境不再因重启而中断
  • windows系统 从 Hugging Face网站上使用 huggingface-cli 命令下载AI大模型到本地
  • 模糊聚类分析方法:从模糊等价矩阵到动态分类
  • 【Java常用】注解与反射_2.反射
  • Windows 图形显示驱动开发-概述
  • .NET 9.0 的 Blazor Web App 项目,进度条 <progress> 组件使用注意事项
  • 相机闪光灯拍照流程分析
  • 生成式AI应用提示注入攻击防御解决方案(上)
  • 渗透测试--文件包含漏洞
  • 编程语言错误处理机制的演变与 Go 的实践