HashMap,Hashtable,ConcurrentHashMap

news2025/1/16 7:58:10

目录

一、多线程使用HashMap的一些线程安全问题

     ①造成数据新增丢失

      ②扩容时候,造成链表成环

二、Hashtable和HashMap的区别 

      ①核心方法加锁

     ②其他语法上面的略微差异 

三、引入ConcurrentHashMap【重要】

①ConcurrentHashMap相比于Hashtable的优势

 Hashtable的缺点:

 ConcurrentHashMap的一些优化措施(jdk1.8往后)

     (1)每一个哈希桶,都是一把"锁"

      ​编辑

     (2)ConcurrentHashMap不针对get(Object key)方法加锁

     (3)ConcurrentHashMap针对部分修改操作,采用了volatile+原子的方式,让写的操作与不加锁的get()方式不会产生锁冲突 

     (4)扩容操作的优化


       这三种数据结构,都是对于哈希表的具体实现。

       有关哈希表的具体设计,我们在前面的文章当中也提到了,关于HashMap的简单源码分析,以及HashMap的具体的一些实现,也在下面这两篇文章当中提到了:

      (1条消息) HashMap简单源码分析_革凡成圣211的博客-CSDN博客https://blog.csdn.net/weixin_56738054/article/details/127786631?spm=1001.2014.3001.5501
(1条消息) Java的手写简单的哈希表_革凡成圣211的博客-CSDN博客https://blog.csdn.net/weixin_56738054/article/details/127416821?spm=1001.2014.3001.5501


下面,将重点分析,多线程使用HashMap的一些问题,以及Hashtable、ConcurrentHashMap和HashMap之间的一些区别。


一、多线程使用HashMap的一些线程安全问题

     ①造成数据新增丢失

        因为HashMap当中,并没有涉及任何的加锁操作,因此:当多个线程同时调用put()的时候,有可能在两个键的哈希值一样的时候,之后调用put()的线程新增的值覆盖掉最开始线程新增的值。

        图解:

        


      ②扩容时候,造成链表成环

       我们了解到,扩容的操作,实际上是HashMap重现初始化一个原来大小2倍的数组,并且根据新的数组的长度,重新哈希的这样一个过程。

        如果执行并发扩容,那么,很有可能在扩容的时候,让哈希表当中某一个哈希桶的链表变成了一个"环"。那么,也就意味着如果想获取某一个元素,对哈希桶对应位置的链表进行遍历的时候,没有任何一个节点的next指针为null,那么将引发死循环。

     


二、Hashtable和HashMap的区别 

      ①核心方法加锁

        Hashtable的核心方法put()和get()方法被加锁了。因此,Hashtable是线程安全的

        


     ②其他语法上面的略微差异 

           (1)HashMap允许null值作为键和值,而Hashtable不允许null作为键和值。

           (2)添加值的哈希算法不同,对于HashMap来说,添加值的哈希算法采用的是内部自定义的一个哈希算法,而Hashtable采用的直接是key.hash()的方式计算出的哈希值。但是负载因子都是0.75.

           (3)初始化的容量不同:HashMap的默认初始容量为16,而Hashtable默认的初始容量为11.

           (4)扩容的方式不同:HashMap采用的是2倍大小的方式扩容,而Hashtable扩容的规则为当前容量*2+1.

           ...这些差异,罗列一部分即可,最重要的还是多线程和单线程的使用环境区别。


三、引入ConcurrentHashMap【重要】

①ConcurrentHashMap相比于Hashtable的优势

对于Hashtable来说,它解决线程安全问题的方式,比较"粗鲁”。

 Hashtable的缺点:


       第一:

       Hashtable使用的直接是synchronized修饰核心方法的方式来加锁的,那么,如果两个线程同时只是读取某一个变量的值,根据之前对线程安全问题的概述,如果线程仅仅只是对变量进行操作而并非操作,那么并不会发生线程安全问题。

       但是Hashtable即使是在多个线程同时读取某个Entry的值的时候,也照样会造成阻塞等待的情况,因此Hashtable的锁的粒度是比较大的。


       第二:

       即使是put()、get()操作,发生线程安全问题的前提条件也必须是需要put()的两个键的哈希值相同的情况,也就是,针对同一个哈希桶进行put()或者get()的情况。

       而Hashtable采用的是直接“一棒打死”。无论是否针对同一个哈希桶进行读写操作,只要多个线程同时调用一个map的put()或者get()方法,都会发生阻塞等待的情况。


 ConcurrentHashMap的一些优化措施(jdk1.8往后)

     (1)每一个哈希桶,都是一把"锁"

       让每一个哈希桶都是一把锁。当新增元素发生哈希冲突,也就是散列到同一个哈希地址的时候,才会发生锁冲突。

       这样,就有效减少了不必要的锁冲突,减小了锁的粒度

      

观察上面的图:当两个线程同时尝试分别修改同一个哈希地址的1,2节点的时候,会产生阻塞等待的情况。

当两个线程同时修改3,4节点的时候,不会产生阻塞等待。

也就是,只有发生了哈希冲突的时候,才会产生阻塞等待的情况 

          观察一下源码:

           


       对于jdk1.8之前的代码,是采用分段锁的方式进行修改的。也就是,其中的N个哈希桶作为一把锁,如果有线程同时针对这N个为一把锁的哈希桶进行修改操作,会产生锁冲突,造成阻塞等待。

     


     (2)ConcurrentHashMap不针对get(Object key)方法加锁

     由于get()方法是通过key得到对应的value的值的方法,本质上是“读取”操作,当多个线程同时调用get()方法的时候,不存在线程安全问题。

     因此,ConcurrentHashMap取消了对于get()方法加锁的机制。

       

 这里需要注意的地方是,ConcurrentHashmap当中:

        Ⅰ一个线程去数据,另外一个线程也同时去取数据,这个时候不存在线程安全问题,也不存在锁冲突

        Ⅱ一个线程去数据,另外一个线程也去数据,不存在线程安全问题,但是有可能存在锁冲突;

        Ⅲ一个线程去数据,另外一个线程去数据,不存在线程安全问题,不存在锁冲突。


        那么,对于第Ⅲ点:

        ConcurrentHashMap对于数据的操作,采用的很多步骤都是CAS,让"写"的很多步骤变成原子的,从而避免了线程安全问题。


     (3)ConcurrentHashMap针对部分修改操作,采用了volatile+原子的方式,让写的操作与不加锁的get()方式不会产生锁冲突 

       ConcurrentHashMap内部充分利用了CAS,来削减了加锁的次数。

       例如,当让存放元素之后,个数+1的时候,采用的是CAS的做法来保证数组当中实际存储key的数量+1这个操作的原子性

        

 其中,addCount方法内部采用的就是CAS的做法来实现个数+1的原子性的。 


     (4)扩容操作的优化

        回顾一下HashMap或者Hashtable扩容的操作,它们都是创建一个更大容量的数组,然后把每一个元素重新哈希的做法。

        在数据量比较大的时候,会造成可能某次put()之后,线程会阻塞等待很长的时间,才可以完成扩容。

       扩容条件:

       ①当存放Node<K,V>节点的数组长度小于64,并且单个哈希桶的链表的存放节点个数达到8的时候,会触发扩容;如果存放节点的数组长度>=64,那么会把当前的链表树化为红黑树。

       ②当存储的实际key的数量/Node<K,V>数组的长度达到负载因子的时候,会触发扩容


      以上两点,和jdk1.8版本的HashMap的扩容前提条件类似,没有太大的差别。

      满足上面的条件之后,会进入到下面的操作:

      首先申请一个原来数组大小2倍的新数组

      如果有多个线程同时尝试扩容,那么,ConcurrentHashMap会对这些线程进行"分工”。何为分工呢?画个图简单看一看:

       也就是,每一个线程,分别对原有的数组上面的元素分别进行"搬运"操作,让它们都被各自的线程重新哈希到新的数组上面的位置,这样的效率会更加的高。


        

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

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

相关文章

著名相声艺术家侯耀华,77岁寿宴现场曝光,郭德纲师哥前去祝贺

在中国的相声界&#xff0c;有一条不成文的规定&#xff0c;关于著名相声表演艺术家的判定&#xff0c;从来不是以相声水平高低为标准。只要你有足够长的寿命&#xff0c;只要你能把其他人都熬走熬败&#xff0c;就算你是一个相声小白&#xff0c;也能摇身一变成为艺术家。 不过…

Git介绍与使用

1.集中式版本控制 svn 中央服务器 所有的版本数据都存在服务器上&#xff0c;用户本地只有自己所同步的版本&#xff0c;如果不联网的话&#xff0c;用户就看不到 SVN是集中式版本控制系统&#xff0c;版本库是集中放在中央服务器的 而工作的时候,用的都是自己的电脑,所以首先…

跨境电商物流系统功能框架

随着国内互联网巨头们逐渐将更多注意力投向了跨境电商市场&#xff0c;电商巨头出海也在掀起新的发展高潮。下面是跨境电商物流系统功能框架&#xff0c;供大家参考1、OMS叫做订单管理系统&#xff08;Order Management System&#xff09;&#xff0c;在不同公司&#xff0c;不…

云原生时代的运维体系进化

云原生已经成为数字经济技术的创新基石&#xff0c;并且正在深刻地改变企业上云和用云的方式。云原生的用云方式可以帮助企业最大化获得云价值&#xff0c;也给企业的计算基础设施、应用架构、组织文化和研发流程带来新一轮变革。而业务和技术挑战也催生了新一代云原生运维技术…

设计模式(一)----设计模式概述及UML图解析

1、设计模式概述 1.1 软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中&#xff0c;而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫亚历山大&#xff08;Christopher Alexander&#xf…

Golang开发 02

文章目录一、Golang开发工具二、visual studio code安装(VS code)1、安装window2、安装mac、linux一、Golang开发工具 # 1、Visual studio code &#xff08;常用&#xff09; # 2、Sublime Text(免费) # 3、Vim # 4、Emacs # 5、Eclipes IDE工具&#xff0c;开源免费&#xf…

数据分析-深度学习PytorchDay1

深度学习框架pytorch学习(一)准备环境准备环境一、深度学习框架简介二、Tensorflow与Pytorch的比较三、安装开发环境一、深度学习框架简介1、Google阵营最早的是由加拿大团队开发的theano一个机器学习库&#xff0c;现在已经停止更新。接着Google开发了Tensorflow&#xff0c;并…

【机器学习知识点】3. 目标检测任务中如何在图片上的目标位置绘制边界框

目录前言导入图片定义边界框绘制函数在图片中绘制边界框总结前言 在图像分类任务中&#xff0c;很多时候我们不仅要知道图像中目标的类别&#xff0c;而且还想知道它们在图像中的具体位置。在计算机视觉里&#xff0c;这类任务被称为目标检测&#xff08;object detection&…

uniapp开发技术

目录 1、js 判断iPhone|iPad|iPod|iOS|Android客户端 2、js实现防抖 3、 js实现节流 4、 页面在弹窗时禁止底部页面滚动&#xff08;h5端&#xff09; touchmove.stop.prevent 5、scrollIntoView 1、js 判断iPhone|iPad|iPod|iOS|Android客户端 // fullScreen代表整个页面…

【C++】STL---list的模拟实现

目录前言一、list和vector的区别二、节点的定义三、list类定义四、push_back函数五、push_front函数六、迭代器七、begin和end函数八、迭代器区间初始化九、迭代器的操作符重载操作符重载操作符- -重载操作符&#xff01;重载操作符重载操作符*重载十、insert函数十一、erase函…

如何应用卫星图像插入到Auto CAD

如何应用卫星图像插入到Auto CAD发布时间&#xff1a;2018-01-17 版权&#xff1a;工具准备BIGEMAP GIS Office&#xff1a;http://www.bigemap.com/reader/download/案例&#xff1a;等高线完美套合卫星影像教程本实例使用AutoCAD2008软件进行影像与矢量数据叠加配准。影像获取…

变压器和特斯拉线圈

目录 变压器用途 变压器的原理 变压器特点 特斯拉线圈用途 特斯拉线圈原理 特斯拉线圈特点 参考&#xff1a; 变压器用途 电压变换、电流变换、阻抗变换、隔离、稳压等 1&#xff09;开关电源&#xff0c;充电器&#xff0c;220v转换为指定电压&#xff0c;以给各类电子…

Revit建模幕墙问题:幕墙添加门/窗和生成幕墙

一、Revit中如何在幕墙当中添加门、窗构件 今天跟大家分享一下幕墙当中添加门窗的方法&#xff0c;这种方法大家可以联想到很多应用上&#xff0c;因为这个既是个方法也是个技巧&#xff0c;好了&#xff0c;我们直接进入主题吧。 首先&#xff0c;我们新建幕墙&#xff0c;给它…

范数的意义与计算方法

1. 范数的意义 范数可以简单的理解为“距离”。由于向量是既有大小又有方向的量&#xff0c;所以向量是不能直接比较大小的&#xff0c;但是范数提供了一种方法&#xff0c;可以将所有的向量转化为一个实数&#xff0c;然后就可以比较向量的大小了。&#xff08;注&#xff1a…

【计算机视觉】Pooling层的作用以及如何进行反向传播

问题 CNN网络在反向传播中需要逐层向前求梯度,然而pooling层没有可学习的参数,那它是如何进行反向传播的呢? 此外,CNN中为什么要加pooling层,它的作用是什么? Pooling层 CNN一般采用average pooling或max pooling来进行池化操作,而池化操作会改变feature map的大小,…

swagger的使用与步骤

1、导入maven工程首先我们创建一个 Spring Boot 项目&#xff0c;并引入 Swagger3 的核心依赖包&#xff0c;如下&#xff1a;<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.…

2022年度游戏本行业数据报告:十大热门品牌销量排行榜

2022年游戏本市场的总体局面是&#xff1a;产品竞争极为激烈&#xff0c;同时又各具特色卖点。今年的游戏本市场&#xff0c;市场格局并未有较大的变化&#xff0c;但是新技术、新产品层出不穷&#xff0c;各个游戏本厂商们通过不断创新、提升产品性能&#xff0c;推出了体验感…

高等数学(第七版)同济大学 习题11-5 个人解答

高等数学&#xff08;第七版&#xff09;同济大学 习题11-5 函数作图软件&#xff1a;Mathematica 1.按对坐标的曲面积分的定义证明公式∬Σ[P1(x,y,z)P2(x,y,z)]dydz∬ΣP1(x,y,z)dydz∬ΣP2(x,y,z)dydz.\begin{aligned}&1. \ 按对坐标的曲面积分的定义证明公式\\\\&…

【javascript】有计算功能的简易计算器

外观不满意&#xff0c;可以自行修改。 主要用到的有&#xff1a;grid布局 js原生 阿里字体图标 eval函数 eval() 函数计算 JavaScript 字符串&#xff0c;并把它作为脚本代码来执行。 如果参数是一个表达式&#xff0c;eval() 函数将执行表达式。如果参数是Javascript语句&a…

Vmware 16 安装 Anolis 8.6

Vmware 安装 Anolis一.Anoliso镜像下载二.Vmware虚拟机安装Anolisos1.新建自定义2.稍后安装操作系统3.选择系统4.设置虚拟机名称和安装位置5.按需修改磁盘大小6.最后点击完成即可7.编辑虚拟机设置8.选择镜像8.开启虚拟机9.安装10.设置root密码并开始安装11.完成后重启12.登录三…