ConcurrentHashMap源码阅读笔记:initTable()方法

news2024/12/27 13:07:07

一、非常重要的sizeCtl属性

initTable()方法的作用是初始化哈希表,初始化哈希表就要有确定哈希表容量、创建哈希表并将哈希表的引用赋值、修改哈希表的阈值等步骤。initTable()方法里面采用了不加锁方式来确保在高并发的环境下创建哈希表的全部步骤都只能由一个线程完成。

Concurrenthashmap源码中有控制标识符“sizeCtl”,它的值代表了目前哈希表的状态:
1、如果sizeCtl = 0,表示哈希表未初始化,并且数组的初始容量是16;
2、如果sizeCtl = -1,表示哈希表正在进行初始化;
3、如果sizeCtl < 0并且sizeCtl != -1,表示哈希表正在扩容,-(1+n)的值为正在完成数组扩容的线程数量;
4、如果sizeCtl > 0,则有两种情况,一是表示如果哈希表未初始化,但是创建则Concurrenthashmap对象时使用了带参构造传入了初始容量,Concurrenthashmap会将初始容量重新计算并且使用sizeCtl 来记录这个初始容量;二是哈希表已经初始完成,则记录的是哈希表的扩容阈值;
相关代码如下:

public ConcurrentHashMap(int initialCapacity) {
        //如果传入的初始容量小于0,抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY :
                //计算大于初始用量的最小的2的幂作为初始容量
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        //使用sizeCtl来保存
        this.sizeCtl = cap;//初始化sizeCtl
    }

二、initTable() 方法的源码解析

先来看initTable()方法的代码:

    /*
     * sizeCtl释义:
     * 值为0,表示数组未初始化,并且数组的初始容量是16
     * 为正数,表示如果数组未初始化,则记录的是数组的初始容量;如果数据已经初始化,则记录的是数组的扩容阈值
     * 为-1,表示数组正在进行初始化
     * 非-1的负数,表示数组正在扩容,-(1+n)的值为正在完成数组扩容的线程数量
     * */
    private final Node<K, V>[] initTable() {
        Node<K, V>[] tab;
        int sc;
        //第①步,判断数组是否未初始化
        while ((tab = table) == null || tab.length == 0) {
            //第②步,用sc保存sizeCtl的值,作为后面CAS的预期值
            //第③步判断sizeCtl的值是否<0,是的话则发现有其他线程在做数据的初始化,让出CPU
            if ((sc = sizeCtl) < 0)
                Thread.yield();

            //走到这里,说明sizeCtl的值大于或等于0,则有两种清况:一是数组未初始化。
            //二是有其他线程在第①步之后,第②步之前将数组初始化完成,此时sizeCtl为数组的扩容阈值.

            //第④步,CAS将SIZECTL值修改为-1,表示本线程开始进行数组的初始化
            //如果修改成功,开始初始化操作;如果修改失败,则表示有其他线程在①②之后抢先修改了SIZECTL
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    /*第⑤步,double check数组是否初始化,这里为什么要采用双重校验呢?
                     *因为在数组初始化完成之后,sizeCtl的值会,被改成数组的扩容阈值,会是一个大于0的值。
                     *所以完全有可能本线程在①之后,有其他线程完成了数组的初始化全过程,
                     * 使得本线程也能进到这个代码块里来。
                     */
                    if ((tab = table) == null || tab.length == 0) {
                        //第⑥步,执行哈希表的创建工作,
                        //确定哈希表的容量
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        //创建哈希表
                        Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
                        table = tab = nt;
                        //第sc=n-n/4,作为扩容的阈值,赋值给sc
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //第⑦步,如果初始化完成,sizeCtl记录的是扩容阈值;
                    //如果初始化失败,则还原sizeCtl
                    sizeCtl = sc;
                }
                //走到这里,说明本线程初始化完成了或者其他线程在本线程的
                //①、②两步之间完成了哈希表的初始化全过程,此时结束循环
                break;
            }
            //走到这里,说明在发现数组未初始化之后,准确初始化之前,
            //有其他线程已经抢先开始初始化了
            //但是其他线程是否将初始化的工作全部正确的完成,并不知道,所以重新开始循环检查
        }
        return tab;
    }

三:initTable() 方法的步骤解析

根据上面的源码,initTable() 方法的详细步骤为:

①判断哈希表是否未初始化,未初始化的话进入第②步。
②记录sizeCtl的值,用作后面CAS的预期值。

③判断sizeCtl的值是否小于0。如果sizeCtl<0则表示有其他线程在本线程执行步骤①之后抢先执行了第④步将sizeCtl的值改为-1,此时本线程则让出CPU。如果sizeCtl >= 0,则有两种可能,一是哈希表未初始化;二是有其他线程在本线程执行步骤①之后已经抢先将哈希表初始化完成,此时此时sizeCtl记录的是哈希表的扩容阈值自然也是大于0的;这两种情况都进入下一步。
④CAS将SIZECTL值修改为-1。如果修改成功,开始初始化操作;如果修改失败,则表示有其他线程在本线程执行③之后抢先修改了SIZECTL为-1,此时重新回到步骤①检查哈希表是否初始化。

④再次哈希表哈希表是否初始化,这里为什么要采用双重校验呢?因为在哈希表初始化完成之后,sizeCtl的值会被改成数组的扩容阈值,满足sizeCtl >= 0。所以完全有可能本线程在①之后②之前,有其他线程完成了数组的初始化全过程,使得本线程也能进到这里来。

⑤如果哈希表还未初始化,则开始创建哈希表等工作。如果哈希表已经初始化,则还原sizeCtl的值。
⑦如果初始化完成,sizeCtl记录扩容阈值。

下面是笔者自己画的流程图:
在这里插入图片描述

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

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

相关文章

Android入门第57天-使用OKHttp多线程制作像迅雷一样的断点续传功能

简介今天我们将继续使用OkHttp组件并制作一个基于多线程的可断点续传的下载器来结束Android OkHttp组件的所有知识内容。在这一课里我们会在上一次课程的基础上增加SQLite的使用以便于我们的App可以暂存下载时的实时进度&#xff0c;每次下载开始都会判断是覆盖式还是续传式下载…

(3)go-micro微服务项目搭建

文章目录一 微服务项目介绍二 go-micro安装1.拉取micro镜像2.生成项目目录三 项目搭建使用DDD模式开发项目&#xff1a;四 最后一 微服务项目介绍 账户功能是每一个系统都绕不开的一部分&#xff0c;所以本次搭建的微服务项目就是账户微服务项目&#xff0c;其中向外暴露的功能…

【C语言航路】第十站:指针进阶(一)

目录 一、字符指针 二、指针数组 三、数组指针 1.数组指针的定义 2.数组名和&数组名 3.数组指针的使用 四、数组参数、指针参数 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 五、函数指针 总结 一、字符指针 我们知道指针有一种类型叫做字符…

Vue3新特性

文章目录一 新特性之组合API1.1 ref&reactive1.2 methods1.3 props和context1.4 完整代码&效果演示二 Vue3新特性之生命周期函数(在setup中)三 父子级组件间数据传递3.1 Provide&Inject四 Fragment&#xff08;碎片&#xff09;一 新特性之组合API 1.1 ref&re…

XMLHttpRequest和Referer

XMLHttpRequest 对象简介 1999年&#xff0c;微软公司发布 IE 浏览器5.0版&#xff0c;第一次引入新功能&#xff1a;允许 JavaScript 脚本向服务器发起 HTTP 请求。这个功能当时并没有引起注意&#xff0c;直到2004年 Gmail 发布和2005年 Google Map 发布&#xff0c;才引起广…

承蒙时光不弃,做个好人!

落幕 2022年博客之星终于要在今晚2023年1月7日24点整落下帷幕&#xff0c;从上个月28号开始&#xff0c;仿佛经历了一场噩梦&#xff0c;本是抱着随便玩玩的心态报了名&#xff0c;没成想&#xff0c;刚开始自投五星之后竟然显示10几名&#xff0c;那是不是我稍加努力就进前十…

狂揽两千星,速度百倍提升,高性能 Python 编译器 Codon 火了

前言 众所周知&#xff0c;Python 是一门简单易学、具有强大功能的编程语言&#xff0c;在各种用户使用统计榜单中总是名列前茅。相应地&#xff0c;围绕 Python&#xff0c;研究者开发了各种便捷工具&#xff0c;以更好的服务于这门语言。 编译器充当着高级语言与机器之间的…

TensorFlow笔记之单神经元完成多分类任务

文章目录前言一、逻辑回归1.二分类问题2.多分类问题二、数据集调用三、TensorFlow1.x1.定义模型2.训练模型3.结果可视化四、TensorFlow2.x1.定义模型2.训练模型3.结果可视化总结前言 记录分别在TensorFlow1.x与TensorFlow2.x中使用单神经元完成MNIST手写数字识别的过程。 一、…

Linux出现ping: www.baidu.com: 未知的名称或服务解决方法

文章目录解决对象方法先找到网关在Windows下进行VMnet8的配置ping成功Linux出现ping: www.baidu.com: 未知的名称或服务解决方法 解决对象 本文的方法用于各位大佬已经用过以下方法仍然无法ping成功 Linux防火墙已关闭和Windows防火墙已经关闭已经配置好 vim /etc/sysconfig/…

手撕C语言理论知识(上)粗略讲解C语言的部分入门知识

目录 C语言的一些基础知识 操作符简介 Scanf的%[ ] 语句&#xff08;分支、循环、goto&#xff09; 函数 C语言的一些基础知识 主函数 - 程序的入口 - main函数有且仅有一个。char - short - int - long - long long - float - double%d - 十进制整型 %u - 无符号整型 %…

【博学谷学习记录超强总结,用心分享|产品经理基础总结和感悟15】

互联网产品设计背后的心理学02&#xff1a;你就是会被其他人的行为所影响一、前言二、实验设计及结果分析三、实验原理四、实验方法总结五、产品设计中的应用六、结束语前文回顾&#xff1a;让人们做出决定并不是信息本身&#xff0c;而是这些信息呈现的背景或情景。我们这个信…

Spring Cloud Alibaba Dubbo(服务远程调用)

一、软件环境 &#xff08;1&#xff09;自己部署服务器 所有软件及服务器自己进行管理提供&#xff0c;可以直接在项目中添加Spring Cloud依赖。推荐 <dependencyManagement> <dependencies> <dependency> <groupId>com.a…

liunx centos9中安装flask并在pycharm中使用图文攻略

liunx centos9中安装flask并在pycharm中使用图文攻略1.首先在liunx的终端中输入2.安装好flask之后就在pycharm创建新的项目处添加flask项目3.点击绿色三角箭头开始运行flask项目4. 然后登录ip地址就出现Hllo world就代表flask环境搭建完成需要注意事项1.首先在liunx的终端中输入…

ngx_thread_pool_init()

ngx_thread_pool_cycle()函数的主要工作是从待处理的任务队列中获取一个任务&#xff0c;然后调用任务对象的handler()函数处理任务&#xff0c;完成后把任务放置到完成队列中&#xff0c;并通过ngx_notify()通知主线程 手写线程池与性能分析 - 知乎 pthread_cond_wait函数的原…

【5G RRC】5G系统消息介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

一键绕过ID锁激活,为什么很多人都会失败?绕ID这一篇就够了

最近阳了所以暂时断更&#xff0c;你们也要注意身体&#xff0c;最好不要阳 现在绕ID的方法已经非常完善&#xff0c;一个小白选手只要有设备就可以正常绕过ID&#xff0c;总的来说绕ID分为两个步骤&#xff1a;第一步是手机的越狱&#xff0c;这里只能是用checkra1n越狱&…

数据在内存中存储☞(超详解)

目录 一.数据类型大家族 1.了解类型的意义 2.数据类型大家族的分类 二.详解☞数据储存之整形 1.储存方式 &#xff08;1&#xff09;.原码反码补码的概念 &#xff08;2&#xff09;.原码反码补码出现的原因&#xff1a; 计算机中只有加法器没有减法器&#xff0c;所有只…

SemanticKITTI: A Dataset for Semantic Scene Understanding of LiDAR Sequences

Paper name SemanticKITTI: A Dataset for Semantic Scene Understanding of LiDAR Sequences Paper Reading Note URL: https://arxiv.org/pdf/1904.01416.pdf TL;DR 2019 ICCV 论文&#xff0c;提出了一个大规模的真实场景 LiDAR 点云标注数据集 SemanticKITTI&#xff…

数字信号处理第六次试验:数字信号处理在双音多频拨号系统中的应用

数字信号处理第六次试验&#xff1a;数字信号处理在双音多频拨号系统中的应用前言一、实验目的二、实验原理和方法1.关于双音多频拨号系统2.电话中的双音多频&#xff08;DTMF&#xff09;信号的产生与检测3.检测DTMF信号的DFT参数选择4.DTMF信号的产生与识别仿真实验三、实验内…

菜鼠的保研总结

1.个人基本情况 本科学校&#xff1a;山东某双非 本科专业&#xff1a;网络工程 成绩排名&#xff1a;1/46 英语成绩&#xff1a;四级529&#xff0c;六级502 科研竞赛&#xff1a;美国大学生数学建模比赛特等奖提名、全国英语翻译比赛三等奖、山东省蓝桥杯java大学生B组三等奖…