Hashmap非线程安全关于hash值冲突处理

目录
  • 前言
  • hash值冲突该怎么处理
    • hashmap的put方法
    • 非线程安全体现

前言

总是觉得对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非线程安全的第二点体现。当然同时读写一个值也可能会存在数据跟期望不一致的情况。这也是非线程安全的表现。

以上就是HashMap的一些相关问题。个人体会还是需要注重细节,自己看源码,才会有更深入的体会。

更多关于Hashmap非线程安全的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java京东面试题之为什么HashMap线程不安全

    目录 01.多线程下扩容会死循环 02.多线程下 put 会导致元素丢失 03.put 和 get 并发时会导致 get 到 null 01.多线程下扩容会死循环 众所周知,HashMap 是通过拉链法来解决哈希冲突的,也就是当哈希冲突时,会将相同哈希值的键值对通过链表的形式存放起来. JDK 7 时,采用的是头部插入的方式来存放链表的,也就是下一个冲突的键值对会放在上一个键值对的前面(同一位置上的新元素被放在链表的头部).扩容的时候就有可能导致出现环形链表,造成死循环. resize 方法的源

  • Java多线程高并发中解决ArrayList与HashSet和HashMap不安全的方案

    1.ArrayList的线程不安全解决方案 将main方法的第一行注释打开,多执行几次,会看到如下图这样的异常信息:

  • ConcurrentHashMap是如何保证线程安全

    目录 JDK 1.7 底层实现 JDK 1.7 线程安全实现 JDK 1.8 底层实现 JDK 1.8 线程安全实现 总结 ConcurrentHashMap 是 HashMap 的多线程版本,HashMap 在并发操作时会有各种问题,比如死循环问题.数据覆盖等问题.而这些问题,只要使用 ConcurrentHashMap 就可以完美解决了,那问题来了,ConcurrentHashMap 是如何保证线程安全的?它的底层又是如何实现的?接下来我们一起来看. JDK 1.7 底层实现 Concurr

  • Java中ConcurrentHashMap是如何实现线程安全

    目录 语法: ConcurrentHashmap 的需要: 如何使 ConcurrentHashMap 线程安全成为可能? Hashtable.Hashmap.ConcurrentHashmap的区别 ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发.此类遵循与 Hashtable 相同的功能规范,并包含 Hashtable 的所有方法.ConcurrentHashMap 位于 java.util.Concurrent 包中. 语法: public class

  • Hashmap非线程安全关于hash值冲突处理

    目录 前言 hash值冲突该怎么处理 hashmap的put方法 非线程安全体现 前言 总是觉得对HashMap很熟悉,但最近连续被问到几个关于它的问题,才发现它其实并不简单.这里对关于它的一些问题做个总结,也希望能够大家一个参考. hash值冲突该怎么处理 都知道它是基于hash值,可以进行常量时间消化的存储结构.广泛用于各种情况下的高效key-value存储.这里有几个问题.首先,如果出现hash值冲突该怎么处理?相信很多人都不用思考就能说出,通过链表来解决冲突.就是将hash值相同值存储到

  • 快速解决Hash碰撞冲突的方法小结

    Hash碰撞冲突 我们知道,对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突.如下将介绍如何处理冲突,当然其前提是一致性hash. 1.开放地址法 开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,-,k(k<=m-1) 其中,m为哈希表的表长.di 是产生冲突的时候的增量序列.如果di值可能为1,2,3,-m-1,称线性探测再散列. 如果d

  • Java线程安全与非线程安全解析

    ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?这些都是Java面试中常见的基础问题.面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的:HashMap是非线程安全的,HashTable是线程安全的:StringBuilder是非线程安全的,StringBuffer是线程安全的.因为这是昨晚刚背的<Java面试题大全>上面写的.此时如果继续问:什么是线程安全

  • Redis字典实现、Hash键冲突及渐进式rehash详解

    目录 Redis字典实现 哈希表节点结构 哈希表结构 字典 哈希算法 解决hash冲突 rehash 渐进式hash 本笔记参考<Redis设计与实现> P24~ 37 Redis字典实现 哈希表节点结构 typedef struct dictEntry { // 键 void *key; // 值 : 可以是一个指针,或者是一个uint64/int64 的整数 union { void *val; uint64_t u64; int64_t s64 } v; // 指向下一个哈希表节点,形成

  • 使用Jedis面临的非线程安全问题详解

    目录 1. jedis类图 2. 为什么jedis不是线程安全的 2.1 共享socket引起的异常 2.2 共享数据流引起的异常 3.jedis多线程操作 网上都说jedis实例是非线程安全的,常常通过JedisPool连接池去管理实例,在多线程情况下让每个线程有自己独立的jedis实例,但都没有具体说明为啥jedis实例时非线程安全的,下面详细看一下非线程安全主要从哪个角度来看. 1. jedis类图 2. 为什么jedis不是线程安全的 由上述类图可知,Jedis类中有RedisInput

  • PHP随机生成唯一HASH值自定义函数

    网上有很多种方法获取随机唯一的HASH值,但是大同小异: 1.先获取随机的唯一字符串 2.进行MD5或者sha1算HASH值 一个项目要用到hash值,就去网上找了找,却发现PHP有一个函数能直接生成唯一字符串--uniqid(),通过使用这个函数,再加上自己生成的随机数(防止被破解),更具有唯一性且不易被猜解.主要考虑问题如下: 1.随机的效率与随机性:rand和mt_rand函数的选择,首选mt_rand,效率高,随机性好: 2.随机次数:选择5次,本来unniqid就是唯一的,加上随机的可

  • PHP 线程安全与非线程安全版本的区别深入解析

    从2000年10月20日发布的第一个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用多进程的工作方式不同的是Windows系统是采用多线程的工作方式.如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程的基础之上的,而非多线程. 一般我们会把PHP配置成以ISAPI的方式来运行,ISAPI是多线程的方式,这样就快多了.但存在一个问题,很多常用的PHP扩展是以Linux/Unix的多进程思想来开发的,这些扩展在ISAP

  • javascript实现获取字符串hash值

    性能很高的计算字符串或文件hash值的函数,比md5速度快得多,自己一直用着,重复的几率为很底,一般的应用足够, var I64BIT_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'.split(''); function hash(input){ var hash = 5381; var i = input.length - 1; if(typeof input == 'string'){ f

  • Python实现通过文件路径获取文件hash值的方法

    本文实例讲述了Python实现通过文件路径获取文件hash值的方法.分享给大家供大家参考,具体如下: import hashlib import os,sys def CalcSha1(filepath): with open(filepath,'rb') as f: sha1obj = hashlib.sha1() sha1obj.update(f.read()) hash = sha1obj.hexdigest() print(hash) return hash def CalcMD5(fi

  • Windows下的PHP安装文件线程安全和非线程安全的区别

    从2000年10月20日发布的第一个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用 多进程的工作方式不同的是Windows系统是采用多线程的工作方式.如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程 的基础之上的,而非多线程.一般我们会把PHP配置成以ISAPI的方式来运行,ISAPI是多线程的方式,这样就快多了.但存在一个问题,很多常用的 PHP扩展是以Linux/Unix的多进程思想来开发的,这些扩展在IS

随机推荐