本篇我们来介绍一个最常用的Map结构——HashMap

关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以....

这里直接贴上地址:

关于hash算法:

Hash算法

Hash时取模一定要模质数吗?

关于HashMap:

深入Java集合学习系列:HashMap的实现原理

漫画:什么是HashMap?

JDK 源码中 HashMap 的 hash 方法原理是什么?

What is the use of Holder class in HashMap?(HashMap.Holder)

JDK7与JDK8中HashMap的实现(jdk8对HashMap的红黑树改进)

读完上面的内容,应该对HashMap有了很深的理解,这里补充1点助于深入的理解:

高并发情况下,HashMap为什么会出现死循环

漫画:高并发下的HashMap

疫苗:JAVA HASHMAP的死循环

上述博客均对多线程情况下的HashMap出现死循环问题的原理进行了解释,但是没有提供demo进行参考,我在网上也试图找相应的demo,但是没有能够100%复现的;这里我们直接参考疫苗:JAVA HASHMAP的死循环中的数据和过程,修改HashMap源码,提供循环demo,并阐述死锁发生的原因,及如何解决死循环:

首先,修改HashMap源码如下:

  1. import java.util.Map;
  2. public class HashMap<K,V>{
  3. private transient Entry[] table;
  4. private transient int size;
  5. private int threshold;
  6. private final float loadFactor;
  7. transient int modCount;
  8. public HashMap(int initialCapacity,float loadFactor){
  9. int capacity = 1;
  10. while(capacity < initialCapacity)
  11. capacity <<= 1;
  12. this.loadFactor = loadFactor;
  13. threshold = (int)(capacity * loadFactor);
  14. table = new Entry[capacity];
  15. }
  16. // 所有的hash值都是1
  17. final int hash(Object k){
  18. return 1;
  19. }
  20. // 让所有的值的索引都是1
  21. static int indexFor(int h,int length){
  22. return 1;
  23. }
  24. public V get(Object key){
  25. Entry<K,V> entry = getEntry(key);
  26. return null == entry ? null : entry.getValue();
  27. }
  28. final Entry<K,V> getEntry(Object key){
  29. int hash = (key == null) ? 0 : hash(key);
  30. for(Entry<K,V> e = table[indexFor(hash,table.length)];
  31. e != null;
  32. e = e.next){
  33. System.out.println("e.getKey():" + e.getKey());
  34. Object k;
  35. if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
  36. return e;
  37. }
  38. }
  39. return null;
  40. }
  41. public V put(K key,V value,boolean delay){
  42. int hash = hash(key);
  43. int i = indexFor(hash,table.length);
  44. for(Entry<K,V> e = table[i];e != null;e = e.next){
  45. Object k;
  46. if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
  47. V oldValue = e.value;
  48. e.value = value;
  49. return oldValue;
  50. }
  51. }
  52. modCount++;
  53. addEntry(hash,key,value,i,delay);
  54. return null;
  55. }
  56. void addEntry(int hash,K key,V value,int bucketIndex,boolean delay){
  57. if((size >= threshold) && (null != table[bucketIndex])){
  58. resize(2 * table.length,delay);
  59. hash = (null != key) ? hash(key) : 0;
  60. bucketIndex = indexFor(hash,table.length);
  61. }
  62. createEntry(hash,key,value,bucketIndex);
  63. }
  64. void createEntry(int hash,K key,V value,int bucketIndex){
  65. Entry<K,V> e = table[bucketIndex];
  66. table[bucketIndex] = new Entry<>(hash,key,value,e);
  67. size++;
  68. }
  69. // 扩容,添加了delay参数,用于在测试过程中进行测试
  70. void resize(int newCapacity,boolean delay){
  71. Entry[] newTable = new Entry[newCapacity];
  72. transfer(newTable,delay);
  73. threshold = (int)(newCapacity * loadFactor);
  74. }
  75. // 原有的HashMap的transfer函数,会导致死循环,且有对象被丢弃
  76. // 添加延时选项,用于手动控制多个线程的相对运行过程
  77. void transfer(Entry<K,V>[] newTable,boolean delay){
  78. System.out.println("transfer in\t" + Thread.currentThread().toString());
  79. int newCapacity = newTable.length;
  80. for(Entry e : table){
  81. while(null != e){
  82. Entry<K,V> next = e.next;
  83. if(delay){
  84. try{
  85. Thread.sleep(20);
  86. }catch(InterruptedException e1){
  87. e1.printStackTrace();
  88. }
  89. }
  90. int i = indexFor(e.hash,newCapacity);
  91. e.next = newTable[i];
  92. newTable[i] = e;
  93. e = next;
  94. }
  95. }
  96. System.out.println("transfer out\t" + Thread.currentThread().toString());
  97. }
  98. // 解决死循环
  99. // 方式: 扩容后的数组,在对象rehash的过程中,如果某个位置出现冲突,采用尾插法将Entry插入链表
  100. // 原理: 出现死循环的原因:在rehash过程中,对原数组某个位置的链表,采用从头开始的遍历方式,在新数组中,如果出现冲突,采用头插法将Entry插入链表
  101. // 注意: 这里虽然解决了死循环的问题,但是因为并发修改对象内容,导致遍历过程中某些对象被丢弃的问题还是存在,
  102. // 所以还是老老实实地用ConcurrentHashMap或者Collections.synchronizedMap()吧
  103. //void transfer(Entry<K,V>[] newTable,boolean delay){
  104. // System.out.println("transfer in\t" + Thread.currentThread().toString());
  105. // int newCapacity = newTable.length;
  106. // for(Entry e : table){
  107. // while(null != e){
  108. // Entry<K,V> next = e.next;
  109. // if(delay){
  110. // try{
  111. // Thread.sleep(20);
  112. // }catch(InterruptedException e1){
  113. // e1.printStackTrace();
  114. // }
  115. // }
  116. // int i = indexFor(e.hash,newCapacity);
  117. // Entry tmp = newTable[i];
  118. // if(tmp == null){
  119. // newTable[i] = e;
  120. // newTable[i].next = null;
  121. // }else{
  122. // // 尾插法
  123. // while(tmp.next != null){
  124. // System.out.println(tmp.next.getKey());
  125. // System.out.println("----------------------");
  126. // tmp = tmp.next;
  127. // }
  128. // tmp.next = e;
  129. // tmp.next = null;
  130. // }
  131. // e = next;
  132. // }
  133. // }
  134. // System.out.println("transfer out\t" + Thread.currentThread().toString());
  135. //}
  136. static class Entry<K,V> implements Map.Entry<K,V>{
  137. final K key;
  138. V value;
  139. Entry<K,V> next;
  140. int hash;
  141. Entry(int h,K k,V v,Entry<K,V> n){
  142. value = v;
  143. next = n;
  144. key = k;
  145. hash = h;
  146. }
  147. public final K getKey(){
  148. return key;
  149. }
  150. public final V getValue(){
  151. return value;
  152. }
  153. public final V setValue(V newValue){
  154. V oldValue = value;
  155. value = newValue;
  156. return oldValue;
  157. }
  158. // hashCode永远为1
  159. public final int hashCode(){
  160. return 1;
  161. }
  162. public final String toString(){
  163. return getKey() + "=" + getValue();
  164. }
  165. }
  166. }
  1. public class HashMapInfinitLoop{
  2. public static void main(String[] args) throws InterruptedException{
  3. HashMap<Integer,Integer> map = new HashMap<>(4,0.8f);
  4. map.put(5,55,false);
  5. map.put(7,77,false);
  6. map.put(3,33,false);
  7. new Thread("Thread1"){
  8. public void run(){
  9. map.put(17,77,true);
  10. System.out.println("put 17 finished");
  11. }
  12. }.start();
  13. new Thread("Thread2"){
  14. public void run(){
  15. map.put(23,33,false);
  16. System.out.println("put 23 finished");
  17. }
  18. }.start();
  19. Thread.sleep(2000);
  20. // 此处get()死循环
  21. System.out.println(map.get(5));
  22. }
  23. }

运行上述demo,控制台输出如下:

  1. transfer in Thread[Thread1,5,main]
  2. transfer in Thread[Thread2,5,main]
  3. transfer out Thread[Thread2,5,main]
  4. put 23 finished
  5. transfer out Thread[Thread1,5,main]
  6. put 17 finished
  7. e.getKey():17
  8. e.getKey():23
  9. e.getKey():3
  10. e.getKey():7
  11. e.getKey():3
  12. e.getKey():7
  13. .......

注释掉原有的transfer(),使用解决死循环的transfer(),运行结果如下:

  1. transfer in Thread[Thread1,5,main]
  2. transfer in Thread[Thread2,5,main]
  3. transfer out Thread[Thread2,5,main]
  4. put 23 finished
  5. transfer out Thread[Thread1,5,main]
  6. put 17 finished
  7. e.getKey():17
  8. e.getKey():23
  9. e.getKey():3
  10. null

发现死循环问题是没有了,但是还是存在数据被丢弃的情况.

so,it sucks

Java容器解析系列(11) HashMap 详解的更多相关文章

  1. Java容器解析系列(13) WeakHashMap详解

    关于WeakHashMap其实没有太多可说的,其与HashMap大致相同,区别就在于: 对每个key的引用方式为弱引用; 关于java4种引用方式,参考java Reference 网上很多说 弱引用 ...

  2. Java容器解析系列(9) PrioriyQueue详解

    PriorityQueue:优先级队列; 在介绍该类之前,我们需要先了解一种数据结构--堆,在有些书上也直接称之为优先队列: 堆(Heap)是是具有下列性质的完全二叉树:每个结点的值都 >= 其 ...

  3. Java容器解析系列(14) IdentityHashMap详解

    IdentityHashMap,使用什么的跟HashMap相同,主要不同点在于: 数据结构:使用一个数组table来存储 key:value,table[2k] 为key, table[2k + 1] ...

  4. Java容器解析系列(7) ArrayDeque 详解

    ArrayDeque,从名字上就可以看出来,其是通过数组实现的双端队列,我们先来看其源码: /** 有自动扩容机制; 不是线程安全的; 不允许添加null; 作为栈使用时比java.util.Stac ...

  5. Java容器解析系列(12) LinkedHashMap 详解

    LinkedHashMap继承自HashMap,除了提供HashMap的功能外,LinkedHashMap还是维护一个双向链表(实际为带头结点的双向循环链表),持有所有的键值对的引用: 这个双向链表定 ...

  6. Java容器解析系列(17) LruCache详解

    在之前讲LinkedHashMap的时候,我们说起可以用来实现LRU(least recent used)算法,接下来我看一下其中的一个具体实现-----android sdk 中的LruCache. ...

  7. Java容器解析系列(0) 开篇

    最近刚好学习完成数据结构与算法相关内容: Data-Structures-and-Algorithm-Analysis 想结合Java中的容器类加深一下理解,因为之前对Java的容器类理解不是很深刻, ...

  8. java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别

    java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...

  9. java基础解析系列(三)---HashMap

    java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

随机推荐

  1. Linux网络IO模型

    同步和异步,阻塞和非阻塞 同步和异步 关注的是结果消息的通信机制 同步:同步的意思就是调用方需要主动等待结果的返回 异步:异步的意思就是不需要主动等待结果的返回,而是通过其他手段比如,状态通知,回调函 ...

  2. CP防火墙配置NAT

    Static NAT配置 Step1:创建host对象并且配置static NAT,如下图: Step2:修改全局属性的NAT项的ARP代理选项,勾选即可,如下图: Step3:在网关的web por ...

  3. video实现有声音自动播放

    video实现自动播放有声音 需求:老板见人家可以的,我们的也要可以!!! 前端:自动播放,简单... 要实现:鼠标移入视频播放同时有声音,移出让你暂停,,,,, 问题集合 1- 自动播放实现没有声音 ...

  4. python密码输入模块getpass

    import getpass pwd = getpass.getpass("请输入密码") print(pwd)

  5. 【一起学源码-微服务】Nexflix Eureka 源码七:通过单元测试来Debug Eureka注册过程

    前言 上一讲eureka client是如何注册的,一直跟到源码发送http请求为止,当时看eureka client注册时如此费尽,光是找一个regiter的地方就找了半天,那么client端发送了 ...

  6. lombok工作原理分析

    在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码.但是自动生成的代码到底是如何产生的呢? 核心之处就是对于注解的解析上.JDK5引入了注解的同时,也提供了两种解析方式. 运行时解 ...

  7. maven常用标签

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  8. StrategyPattern(策略模式)-----Java/.Net

    在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 ...

  9. UI自动化和selenium相关以及八大定位

    一.UI自动化相关 1. UI自动化的本质(重点) 定位元素→操作元素→模拟页面操作→断言→测试报告 2. 适合UI自动化的场景 UI自动化的前提条件 (1)需求不能频繁变动 (2)UI稳定(UI自动 ...

  10. POJ2186 Popular Cows 题解 强连通分量

    题目链接:http://poj.org/problem?id=2186 题目大意: 每头牛都想成为牛群中的红人. 给定N头牛的牛群和M个有序对(A, B),(A, B)表示牛A认为牛B是红人: 该关系 ...