Hashmap非线程安全关于hash值冲突处理
作者:zziawan 发布时间:2023-11-11 09:22:10
前言
总是觉得对HashMap很熟悉,但最近连续被问到几个关于它的问题,才发现它其实并不简单。这里对关于它的一些问题做个总结,也希望能够大家一个参考。
hash值冲突该怎么处理
都知道它是基于hash值,可以进行常量时间消化的存储结构。广泛用于各种情况下的高效key-value存储。这里有几个问题。首先,如果出现hash值冲突该怎么处理?相信很多人都不用思考就能说出,通过链表来解决冲突。就是将hash值相同值存储到一个挂载在该位置的链表里面去。但是这就又引出了新的问题,给一个key,它的hash值有冲突的情况下,它是如何在链表上取到你所期望的value的?这个就要去看hashmap的存储结构了。每一个key-value会被封装到一个entity中,map中存储的其实是这个entity。这样既存储了value,又存储了key。所有在链表上取值只需要比较key是否equal。这里又出现了一个问题,为什么建议重写key的equal和hash方法。重写hash方法能够保证不同对象用于不同的hash值,从而减少冲突,重写equal则可以在出现冲突的情况下,保证不出现错误覆盖的情况。
hashmap的put方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//没有初始化,则要初始化,初始化调用的也是resize()方法
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//这个位置没有值,直接插入,重写hash方法的作用体现在这里
else {//出现了冲突
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//找到了如该key equal的value
e = p;
else if (p instanceof TreeNode)//事树节点,插入到树里。jdk8冲突节点数目达到一定值后,使用树结构存储
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {//一直找到链表的结尾,将k-v插入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//是否需要改成树存储
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
上面的代码充分表明了key重写equal的作用。HashMap以key的value来获取值,所以一定要确保同一个key的hashcode在任何时候都不能改变。这也是为什么建议key是不可变对象,如string对象。如果key是对象,运行过程中key的hashcode因为内部值的改变而发生了改变,那么map中的value将永远丢失。
非线程安全体现
以上是关于kv的讨论,接下来是关于HashMap的另外的一个常见话题——线程安全问题。多数人都知道它是非线程安全的。但是如果问你它非线程安全体现在哪里,恐怕会难住一批人。
首先容易想到的是多线程插入时出现了冲突的情况,多个线程同时插入,但是这其中有些值又出现了冲突。插入时大家都看到这个位置没有值,于是都进行插入,这样肯定会出现值覆盖,对外的表现就是值丢失。如果开始插入时,这个位置已经有了值,那么在插入链表过程中还是会出现值覆盖。
另外就是同时扩容问题。因为HashMap会在空间不足时自动扩容,大小变成之前的两倍。同时复制之前的值到新的数组中。冲突链也会进行复制。如果多个线程插入后同时看到容量需要调整,就都会调用resize方法。那么底层到达会出现什么问题就难以预测了。
所以这也是HashMap非线程安全的第二点体现。当然同时读写一个值也可能会存在数据跟期望不一致的情况。这也是非线程安全的表现。
来源:https://www.cnblogs.com/zziawanblog/p/7231784.html


猜你喜欢
- 在C语言中一般用typedef来为回调函数定义别名(参数名)。 别名通过宏定义typedef来实现,不是简单的宏替换。可以用作同
- Android里判断是否可以上网,常用的是如下方法:/** * 检测网络是否连接 * * @return */private boolea
- 我们的日常开发中经常用到下拉刷新,而网上评价最好的开源下拉刷新组件当然还是android-Ultra-Pull-To-Refresh 此组件
- 话不多说,请看代码:using System;using System.Web;using System.Drawing;using Sys
- 奇怪的不等于(≠)最近,栈长用 IntelliJ IDEA 看源码时发现:咦~这是什么鬼?Java 不等于的写法不是一直都是 != 么?什么
- 本文实例讲述了Android TextView中文字通过SpannableString设置属性的方法。分享给大家供大家参考,具体如下:在An
- 1.System.currentTimeMills():得到当前时间距离时间原点的毫秒数,返回值是Long类型的整数。代码演示:public
- 本文实例为大家分享了java实现通过绑定邮箱找回密码功能,供大家参考,具体内容如下1.输入用户名及验证码,验证用户名是否存在(1).生成验证
- 本文通过老王和小王买车,引出设计模式中的结构型设计之桥接模式,接着说明设计型模式的概念和代码实现,为了加深理解,会说明适配器设计模式在JDB
- 最近公司要求开发工具要用Idea,作为一个eclipse的老员工,记录一下Idea中遇到的坑刚开始用Idea从Git上导入一个项目时,遇到了
- 本文实例讲述了Android实现的简单蓝牙程序。分享给大家供大家参考,具体如下:我将在这篇文章中介绍了的Android蓝牙程序。这个程序就是
- 一、Has方法与With方法如:A类必须包含B类一个不为null的实例,而B类可选择时候包含A类一个实例。A.HasRequired(a =
- 要求:1.配置文件的namespace名称空间指定为接口的全类名2.配置文件中的id唯一标识与接口中的方法对应(返回值类型对应,方法名对应,
- 本文实现Unity调用手机摄像,拍摄,然后识别二维码,显示二维码的内容。需要导入一个zxing.unity.dll文件,现在这个脚本的识别数
- 本文实例讲述了android监听返回按钮事件的方法。分享给大家供大家参考。具体如下:用户在点击手机的返回按钮时,默认是推出当前的activt
- 希尔排序是插入排序的一种,又称"缩小增量排序”,是插入排序算法的一种更高效的改进版本。希尔排序原理1.选定一个增长量h,按照增长量
- 一、自动装配1、四种类型的自动装配类型解释xml 配置byName根据 Bean 的 name 或者 id<bean id=”bean
- 前言:在看这个变更之前,我们需要回忆下 Android 12 的一个安全性变更, 即声明了 <intent-filter&g
- 本人所使用的开发环境是VS2008,开发的系统所在移动终端版本为windows mobile 5.0。由于需要进行身份的验证,需要获取移动终
- 前言在产品发布前夕,经常因为编写各类设计文档感到心碎,倒不是难,而是比较繁琐,举例来说,像编写数据库文档这种操作来说,对于新手,甚至很多有一