JAVA并发编程之ConcurrentHashMap详解

news2025/3/10 18:27:39

ConcurrentHashMap

一、ConcurrentHashMap写入数据流程

一般在项目中使用ConcurrentHashMap时,都是作为JVM缓存使用的。

ConcurrentHashMap是线程安全的。如果你项目涉及到了多个线程都会操作key-value结构时,别用HashMap,一定要上ConcurrentHashMap。

在方法局部内,只有当前线程使用时,才可以用HashMap。

ConcurrentHashMap的存储结构?

存储结构是数组 + 链表 + 红黑树。

image.png

put操作流程:

  • 将key进行散列运算,计算出具体的hash值。后面要基于这个hash值确定数据存放的索引位置。
  • 循环开始-------------------------------------------------------------------
  • 查看ConcurrentHashMap的 数组是否已经初始化 。(数组全局唯一,懒加载)
    • 初始化就继续尝试存放数据。
    • 没初始化,那就先尝试将数组new出来。
  • 基于hash值,确认数据要存放的索引位置
    • 如果数组索引位置没数据,将当前数据扔进去。优先将key-value封装为一个Node对象。这里为了保证线程安全, 直接采用CAS的方式,将当前索引位置的null替换为指定的Node数据
      • 如果CAS成功,循环结束
      • 如果CAS失败,重新走循环
    • 如果数组索引有数据,往后走其他流程
  • 如果有数据,查看当前数据的状态 是否是扩容的情况 。MOVED(hash = -1)
    • 如果正在扩容,当前线程去 协助扩容 ,加快扩容速度。协助扩容结束后,重新走循环。
  • 如果有数据,没有扩容操作影响到当前线程。
    • 加锁,基于数组索引位置的Node对象作为 synchronized的锁
      • 将数据 挂到链表的末端
        • 添加数据到链表后,会查看链表长度,是否达到了8个,就需要尝试链表转红黑树 。CHM要求必须数组长度 ≥ 64 && 链表长度达到8才会转换。
      • 将数据 添加到红黑树 结构中。TREEBIN(hash = -2)
  • 循环结束---------------------------------------------------------------------
  • 需要记录元素个数,为了保证线程安全,这里采用了 LongAdder做计数器
  • 添加数据结束。

二、ConcurrentHashMap扩容流程&协助扩容

ConcurrentHashMap扩容操作只有两个事情:

  • 构建新数组,长度是老数组长度的二倍。(new)
  • 将老数组中的数据,迁移到新数组中。

区分第一个来扩容和协助扩容的方式:

为了区分谁是第一个来扩容的线程,谁是来协助扩容的线程,需要掌握一个属性,这个属性就是sizeCtl

private transient volatile int sizeCtl;

第一个来扩容的线程会优先修改sizeCtl的值,将其修改为一个<-1的值。

其他所有来协助扩容的线程,发现sizeCtl<-1,直接对sizeCtl做 + 1操作,代表我来扩容了。

修改sizeCtl的操作,是基于CAS完成的。

第一个来扩容的线程,由他去初始化新数组,并且做迁移数据的操作。协助扩容的线程,就是来帮忙将老数组的数据迁移到新数组的。


整体扩容的流程(以第一个来扩容的线程)

  • 计算每次迁移多长索引位置的数据到新数组( 步长 ),会根据CPU内核数和数组长度来计算,最小是16。
  • 会由 第一个来扩容的线程初始化新数组 ,长度为原来的二倍。
  • 迁移数据循环开始-------------------------------------------------------------------------
    • 扩容的线程开始 领取迁移数据的任务
      • 领取到任务的,准备干活。
      • 没领取到任务,可以退出扩容
    • 没领取到的查看一下, 扩容结束了么?
      • 结束了,说明当前线程是最后一个退出扩容的线程, 整体检查一遍 ,退出滴干活。
      • 没结束呢,sizeCtl - 1 ,退出的滴干活。
    • 领取到迁移数据操作,在迁移任务中某一个索引位置时,会有不同的情况
      • 当前 索引位置没数据 ,直接仍一个MOVED(hash = -1)
      • 当前 索引位置是MOVED ,啥事不做。(扩容操作最后会有一个大检查)
      • 当前位置有数据,锁住当前位置:
        • 链表迁移新数组
        • 红黑树迁移到新数组 (红黑树会保留一个双向链表,利用双向链表迁移的)
  • 迁移数据循环结束-------------------------------------------------------------------------

三、ConcurrentHashMap查询数据流程

ConcurrentHashMap本来就是作为JVM缓存使用的,对查询速度要求极高。所以在ConcurrentHashMap中 查询操作永远不会阻塞 。无论什么情况,都能去查数据。

查询数据的操作无非就是几种情况:

  • 数据在数组上呢,查的嗷嗷快。
  • 数据在链表上呢,next,next的找,找到返回。
  • 如果数组上的Node是MOVED状态, 代表数据已经迁移到新数组上了,直接去新数组上查询数据
  • 如果数组上的Node是RESERVED状态, 代表当前位置被占了,但是value还在计算中,返回null
  • 数据在红黑树上呢:
    • 如果此时 有写线程正在平衡红黑树或者是等待平衡红黑树,那么读线程会去查询保留的双向链表
    • 如果此时 没有写线程正在平衡或等待平衡操作,直接去红黑树中找数据 。(如果此时有写线程想操作红黑树,那么需要等到读线程完毕后,才可以操作)

lockState == 1:有写线程正在平衡红黑树。

lockState == 2:代表写线程在等着平衡红黑树。

lockState >= 4:代表有读线程在读取红黑树中的数据。(读线程来读数据,就对lockState + 4)

红黑树是一个平衡的二叉树,怎么保持平衡的?

为了保证平衡,写操作可能会旋转某个节点,导致节点的指针发生变化。如果此时读线程在红黑树中遍历找数据,结果写线程改变了红黑树的指针,导致无法找到对应的数据。

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

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

相关文章

【Java多线程】线程中几个常见的属性以及状态

目录 Thread的几个常见属性 1、Id 2、Name名称 3、State状态 4、Priority优先级 5、Daemon后台线程 6、Alive存活 Thread的几个常见属性 1、Id ID 是线程的唯一标识,由系统自动分配,不同线程不会重复。 2、Name名称 用户定义的名称。该名称在各种…

Shellcode免杀对抗(Python)

Shellcode Python免杀,绕过360安全卫士、火绒安全、Defender Python基于cs/msf的上线 cs 执行代码2种可供选择 执行代码 1: rwxpage ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x1000, 0x40) ctypes.windll.kernel32.RtlMoveMemory…

线程安全性的原理分析学习

初步认识Volatile 一段代码引发的思考 下面这段代码,演示了一个使用volatile以及没使用volatile这个关键字,对于变量更新的影响 package com.sp.demo;/*** author : lssffy* Description :* date : 2024/2/16 18:42*/ public class VolatileDemo {publi…

阿里云香港轻量应用服务器是什么线路?cn2?

阿里云香港轻量应用服务器是什么线路?不是cn2。 阿里云香港轻量服务器是cn2吗?香港轻量服务器不是cn2。阿腾云atengyun.com正好有一台阿里云轻量应用服务器,通过mtr traceroute测试了一下,最后一跳是202.97开头的ip,1…

C++学习Day06之继承基本语法

目录 一、程序及输出1.1 没有继承1.2 使用继承 二、分析与总结 一、程序及输出 想象在移动端看资讯,顶部、底部、左侧和中间内容,左侧滑动栏有新闻、体育…,点击不同的新闻,中间内容呈现不同主题的文字叙述,在代码里该…

vivado RAM HDL Coding Techniques

Vivado synthesis可以解释各种RAM编码风格,并将它们映射到分布式RAM中或块RAM。此操作执行以下操作: •无需手动实例化RAM基元 •节省时间 •保持HDL源代码的可移植性和可扩展性从编码示例下载编码示例文件。 在分布式RAM和专用RAM之间的选择块存储器…

在外包干了两年的点点点,人快废了。。。

🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 简单的说下,我大学的一个同学,毕业后我自己去了自研的公司,他…

Java面向对象案例之设计用户去ATM机存款取款(三)

需求及思路分析 业务代码需求: 某公司要开发“银行管理系统”,请使用面向对象的思想,设计银行的储户信息,描述存款、取款业务。 储户类的思路分析: 属性:用户姓名、密码、身份证号、账号、帐户余额 方法&a…

对前端限流操作(Redis版本)4种算法

固定时间窗口算法 固定时间窗口算法也可以叫做简单计数算法。网上有很多都将计数算法单独抽离出来。但是笔者认为计数算法是一种思想,而固定时间窗口算法是他的一种实现包括下面滑动时间窗口算法也是计数算法的一种实现。因为计数如果不和时间进行绑定的话那么失去…

【大厂AI课学习笔记】【2.2机器学习开发任务实例】(3)数据准备和数据预处理

项目开始,首先要进行数据准备和数据预处理。 数据准备的核心是找到这些数据,观察数据的问题。 数据预处理就是去掉脏数据。 缺失值的处理,格式转换等。 延伸学习: 在人工智能(AI)的众多工作流程中&#…

2024.02.18作业

1. 使用fgets统计给定文件的行数 #include <stdio.h> #include <stdlib.h> #include <string.h>int main(int argc, char const *argv[]) {if (argc ! 2){puts("input file error");puts("usage:./a.out filename");return -1;}FILE* f…

单片机学习笔记---AD/DA工作原理(含运算放大器的工作原理)

目录 AD/DA介绍 硬件电路模型 硬件电路 运算放大器 DA原理 T型电阻网络DA转换器 PWM型DA转换器 AD原理 逐次逼近型AD转换器 AD/DA性能指标 XPT2046 XPT2046时序 AD/DA介绍 AD&#xff08;Analog to Digital&#xff09;&#xff1a;模拟-数字转换&#xff0c;将模拟…

【MySQL】学习多表查询和笛卡尔积

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-N8PeTKG6uLu4bJuM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

JMeter接口测试数据分离驱动应用

步骤&#xff1a; 创建csv文件&#xff0c;编写接口测试用例 新建线程组——创建循环控制器&#xff08;循环次数填用例总数&#xff09; 创建CSV数据文件设置&#xff0c;设置参数。&#xff08;注意&#xff1a;是否允许带引号&#xff1f;&#xff1a;一定要设置为true&a…

10M上下文,仅靠提示就掌握一门语言,Google Gemini 1.5被OpenAI抢头条是真冤

这两天&#xff0c;几乎整个AI圈的目光都被OpenAI发布Sora模型的新闻吸引了去。其实还有件事也值得关注&#xff0c;那就是Google继上周官宣Gemini 1.0 Ultra 后&#xff0c;火速推出下一代人工智能模型Gemini 1.5。 公司首席执行官 Sundar Pichai携首席科学家Jeff Dean等众高…

Python输出改变字体颜色方法(附颜色大全)

一、使用示例 print("\033[31m红色字体\033[0m") print("\033[32m绿色字体\033[0m") print("\033[33m黄色字体\033[0m") print("\033[34m蓝色字体\033[0m") print("\033[35m紫色字体\033[0m") print("\033[36m青色字体…

驶向未来:3D可视化模型重塑我们的道路认知

在科技的浪潮中&#xff0c;每一个革新都是对人类未来生活的深度洞察。而今&#xff0c;当可视化这一技术走进我们的视野&#xff0c;它不仅是一场视觉盛宴&#xff0c;更是一次对未来出行方式的全新探索。 一、从平面到立体&#xff0c;解锁道路新视角 你是否曾站在十字路口&…

【Python如何在列表中随机抽出一个元素】

1、python代码如下&#xff1a; import random a [2, 4, 8, 9, "whats up"] q random.choice(a) # 随机从列表a中输出一个元素 b random.choices(a) # 随机从列表a中取出一个元素输出一个列表 lucky_num random.randint(1, 50) # 随机从1-50中取出一个整数包…

解释 OpenAI Sora 的时空补丁:关键因素

人工智能如何将静态图像转换为动态、逼真的视频&#xff1f;OpenAI 的 Sora 通过创新地使用时空补丁来引入答案。 在快速发展的生成模型领域&#xff0c;OpenAI 的 Sora 脱颖而出&#xff0c;成为一个重要的里程碑&#xff0c;有望重塑我们对视频生成的理解和能力。我们解读了…

【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…