HashMap简单源码及多线程下的死循环
主要记录hashMap的一些基本操作源码实现原理以及多线程情况下get()操作的死循环引发原因
一、hashMap简介
1.hashMap集合的主要属性及方法
(默认初始化容量)DEFAULT_INITIAL_CAPACITY = 16
(默认最大容量)MAXIMUM_CAPACITY = 1 << 30
(默认加载因子)DEFAULT_LOAD_FACTOR = 0.75f
(Entry数组)Entry[] table
(Entry实例的数量)size
put(K key, V value)方法
get(K key)方法
2.hashMap结构及操作(new方法 put方法 get方法):
数组+链表的形式,以实例Entry<K,V>的形式存储
a.new方法:
从图中我们可以看到一个hashmap就是一个数组结构,当新建一个hashmap的时候,就会初始化一个数组(默认长度16,加载因子0.75):
源码:
/*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR;threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);table = new Entry[DEFAULT_INITIAL_CAPACITY];init();}
例:table[0],table[1],table[2],table[3]...table[15]
b.put方法
当执行put方法时,会先计算key值的hash值,将hash值和(数组长度减一得到的值)进行与运算,得到数组下标值,将键值对以Entry实例的形式放入数组中:
put方法源码:
/*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or* <tt>null</tt> if there was no mapping for <tt>key</tt>.* (A <tt>null</tt> return can also indicate that the map* previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value) {if (key == null)return putForNullKey(value);int hash = hash(key.hashCode()); //计算key的hash值int i = indexFor(hash, table.length); //根据hash值和数组长度计算数组位置for (Entry<K,V> e = table[i]; e != null; e = e.next) { //如遇到hash冲突(e不为空),遍历链表 Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如遇到key值相等的,进行替换V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}
indexFor方法源码(计算数组下标),方法和简单,就把key值的hash值和数组长度-1做了与运算
/*** Returns index for hash code h.*/static int indexFor(int h, int length) {return h & (length-1);}
例:添加键值对为7,77,调用方法put(7,77),7经过计算后的hash值为7(可自行验证,不多做解释),调用indexFor方法进行与运算:0111&1111=0111,下标值为7。
table[0],table[1],table[2],table[3]...table[7]=7...table[15]
再添加元素8,put(8,88),15经过计算后的hash值仍为8,1000&1111=1000,下标值为8。
table[0],table[1],table[2],table[3]...table[7]=7,table[8]=8...table[15]
再添加元素22,put(22,2222),22经过计算后的hash值为23,10111&1111=7,下标值为7,此时由于table[7]中已存在元素(7,77),产生hash冲突,hashMap会将23放入table[7]中,再执行(e=e.next),以链表形式将next指针指向之前的元素(7,77),如下图所示:
当Entry的个数超过最大容量值*负载因子(16*0.75=12)时,hashMap会进入resize方法,重新创建一个数组并扩容为原来的2倍,再将数据拷贝到新的数组中,如下图所示:
resize方法源码:
/*** Rehashes the contents of this map into a new array with a* larger capacity. This method is called automatically when the* number of keys in this map reaches its threshold.** If current capacity is MAXIMUM_CAPACITY, this method does not* resize the map, but sets threshold to Integer.MAX_VALUE.* This has the effect of preventing future calls.** @param newCapacity the new capacity, MUST be a power of two;* must be greater than current capacity unless current* capacity is MAXIMUM_CAPACITY (in which case value* is irrelevant).*/void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}Entry[] newTable = new Entry[newCapacity];transfer(newTable);table = newTable;threshold = (int)(newCapacity * loadFactor);}
c.get方法:
当执行get方法时,根据key值的hash值,得到数组下标,将数组内的entry的key值与get中的参数做对比(如该数组内有链表,则会继续遍历链表),若hash相等且equals,则返回。
get方法源码:
/*** Returns the value to which the specified key is mapped,* or {@code null} if this map contains no mapping for the key.** <p>More formally, if this map contains a mapping from a key* {@code k} to a value {@code v} such that {@code (key==null ? k==null :* key.equals(k))}, then this method returns {@code v}; otherwise* it returns {@code null}. (There can be at most one such mapping.)** <p>A return value of {@code null} does not <i>necessarily</i>* indicate that the map contains no mapping for the key; it's also* possible that the map explicitly maps the key to {@code null}.* The {@link #containsKey containsKey} operation may be used to* distinguish these two cases.** @see #put(Object, Object)*/public V get(Object key) {if (key == null)return getForNullKey();int hash = hash(key.hashCode()); //计算hash值for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) { //这里遍历数组中的链表,若找到key值相等的,则返回 Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k)))return e.value;}return null;}
二、Java多线程下的HashMap死循环
摘自:http://blog.csdn.net/xiaohui127/article/details/11928865
正常的ReHash的过程
画了个图做了个演示。
- 我假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。
- 最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。
- 接下来的三个步骤是Hash表 resize成4,然后所有的<key,value> 重新rehash的过程
并发下的Rehash
1)假设我们有两个线程。我用红色和浅蓝色标注了一下。
我们再回头看一下我们的 transfer(resize方法中)代码中的这个细节:
do {Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了 int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;} while (e != null);
而我们的线程二执行完成了。于是我们有下面的这个样子。
注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。
2)线程一被调度回来执行。
- 先是执行 newTalbe[i] = e;
- 然后是e = next,导致了e指向了key(7),
- 而下一次循环的next = e.next导致了next指向了key(3)
3)一切安好。
线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。
4)环形链接出现。
e.next = newTable[i] 导致 key(3).next 指向了 key(7)
注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
于是,当我们的线程一调用到,HashTable.get(11)时,悲剧就出现了——Infinite Loop。
转载于:https://www.cnblogs.com/nsxqf/p/6580982.html
HashMap简单源码及多线程下的死循环相关推荐
- vfp赋值超过7位出错_JDK1.7下的HashMap的源码分析
源码分析jdk1.7下的HashMap 我们都知道1.7版本的hashmap的底层是数组加链表构成的,那么今天我们就来自己分析一波源码~ 篇幅有点长,废话不多说,直接开始分析~ 「属性声明」 //初始 ...
- HashMap的源码分析(不作标题党)
HashMap是我们经常用的数据结构,采用了key-value的方式来存储数据.在JDK 1.8的版本中也是对其做了优化修改,现在我们来通过在JDK 1.8的环境下的源代码分析一下HashMap的工作 ...
- hashmap的特性?HashMap底层源码,数据结构?Hashmap和hashtable ConcurrentHashMap区别?
1.hashmap的特性? 允许空键和空值(但空键只有一个,且放在第一位) 元素是无序的,而且顺序会不定时改变 key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 ha ...
- 浅谈对HashMap的理解,以及对HashMap部分源码的分析
文章目录 一.什么是HashMap 1.1 Hash是什么 1.2 Map是什么 Map的特点 Map和Hash的结合 二.HashMap部分源码理解 2.1 关键变量 2.2 关键逻辑 2.3 关键 ...
- HashMap 底层源码细致分析
JDK集合HashMap 底层源码细致分析 前言 提示:对于初始 HashMap 的小伙伴来说,不推荐直接硬啃,建议先看一下如下几个视频教程之后再回头好好理解.(一遍看不懂则反复看,一小块一小块的找对 ...
- Java基础笔记(2)——HashMap的源码,实现原理,底层结构是怎么样的
Java基础笔记(2)--HashMap的源码,实现原理,底层结构是怎么样的 HashMap的源码,实现原理,底层结构 1.HashMap: HashMap是基于哈希表的 Map 接口的实现.此实现提 ...
- 面试官系统精讲Java源码及大厂真题 - 31 AbstractQueuedSynchronizer 源码解析(下)
31 AbstractQueuedSynchronizer 源码解析(下) 低头要有勇气,抬头要有底气. 引导语 AQS 的内容太多,所以我们分成了两个章节,没有看过 AQS 上半章节的同学可以回首看 ...
- 量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)(转)
量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python) 原文地址:http://blog.csdn.net/u012234115/article/details/728300 ...
- 终于搞懂HashMap的源码了!!!
背景 HashMap是我们在平时开发最常用的容器之一,但是我们有真正了解过他吗?他是线程安全的吗?他是以何种方式来存储的呢?为什么初始化的容器大小时2的n次幂呢?他是如何进行扩容的呢?他是如何实现并发 ...
- HashSet源码解析(最好先看HashMap的源码解析)
HashMap的源码解析:https://mp.csdn.net/console/editor/html/106188425 HashSet:Java中的一个集合类,该容器不允许包含重复的数值 pub ...
最新文章
- android闪退日志收集
- Shiro+springboot+mybatis+EhCache(md5+salt+散列)认证与授权-03
- linux管理员常用的命令分享
- 网页设计上机考试原题_《网页设计》上机考试试题
- AI芯片格局最全分析
- hadoop--日志聚集功能的配置
- hdu5354 Bipartite Graph
- swoole php 微信推送,基于thinkphp5、swoole和easywechat微信模板消息推送
- java股票公式源码_各种涨停公式源码
- 计算机专业英语词汇分类收录
- Android开发打开手机自带浏览器
- win7修改网络计算机名字,Win7系统中不能修改计算机名字是怎么回事?
- SQLmap Tamper编写方法(笔记)
- ef连接mysql报root没有权限_EF下使用自定义的connectionString避免数据库密码泄露
- Realtek WiFi定频工具使用操作指南(rtl8188au/rtl8812au/rtl8192cu)
- HTML5期末大作业:家居/家电/家居网站设计——html家具装饰网站设计30页(含论文) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机...
- 管理信息系统【一】之 管理信息系统概论
- react 使用 Youtube 播放器
- django的admin速度慢优化:date_hierarchy、list_filter
- linux mp3 乱码,linux下mp3乱码终极解决方案
热门文章
- Windows系统过滤病毒功能吗
- 8.9 元学习网络结构讲解
- 5.3傅立叶变换意境级讲解
- 无法添加外键约束的原因(cannot add foreign key constraint)
- Spark:Spark 编程模型及快速入门
- 【机器学习笔记】使用lightgbm画并保存Feature Importance
- python里面两个大于号_听说92.8%的人答不对这道Python题,我不信,后来我信了!真有趣...
- 主角有智能芯片的种田小说_5本搞笑玩梗的良品小说,文风轻松幽默,一本正经地逗你笑...
- JavaWeb—作业【建立新闻数据库以及插入数据】
- D - Send a Table (UVA - 10820)