今天想知道HashMap为什么在多线程下不安全,找了许多资料,终于理解了。

首先先了解一下HashMap:

HashMap实现的原理是:数组+链表

HashMap的size大于等于(容量*加载因子)的时候,会触发扩容的操作,这个是个代价不小的操作。

为什么要扩容呢?

HashMap默认的容量是16,随着元素不断添加到HashMap里,出现hash冲突的机率就更高,那每个桶对应的链表就会更长,

这样会影响查询的性能,因为每次都需要遍历链表,比较对象是否相等,一直到找到元素为止。

为了提升查询性能,只能扩容,减少hash冲突,让元素的key尽量均匀的分布。

在单线程中,HashMap是安全的,但是在高并发的环境下,会出现不安全,原因在于HashMap的扩容。

我们先看下HashMap扩容的代码:

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);
}

  

transfer方法就是进行HashMap的扩容的核心方法:

void transfer(Entry[] newTable) {  Entry[] src = table;  int newCapacity = newTable.length;  for (int j = 0; j < src.length; j++) {  Entry<K,V> e = src[j];  if (e != null) {  src[j] = null;  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);  }  }
}  

在并发情况下进行扩容,有一个线程执行到

Entry<K,V> next = e.next;  

而另外一个线程已经执行完扩容,再等这个线程执行完就会出现环路,并且也会丢失一些节点。

我查看一下陈皓大神的文章,里面写的很详细:

https://coolshell.cn/articles/9606.html

正常的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代码中的这个细节:

1
2
3
4
5
6
7
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/somelog/p/9299056.html

Java基础:详解HashMap在多线程下不安全相关推荐

  1. Java集合详解4:HashMap和HashTable

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  2. Java基础——Java NIO详解(一)

    一.基本概念 1.I/0简介 I/O即输入输出,是计算机与外界世界的一个借口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过str ...

  3. Java基础——Java NIO详解(二)

    一.简介 在我的上一篇文章Java NIO详解(一)中介绍了关于标准输入输出NIO相关知识, 本篇将重点介绍基于网络编程NIO(异步IO). 二.异步IO 异步 I/O 是一种没有阻塞地读写数据的方法 ...

  4. Java基础——Java IO详解

    一.概述 1.Java IO Java IO即Java 输入输出系统.不管我们编写何种应用,都难免和各种输入输出相关的媒介打交道,其实和媒介进行IO的过程是十分复杂的,这要考虑的因素特别多,比如我们要 ...

  5. Java方法详解(基础)

    Java方法详解(基础) 什么是方法? System.out.println():调用系统类标准输出对象方法out. 方法是语句的集合,他们在一起执行一个功能. 方法是解决一类问题的步骤的有序组合. ...

  6. Java集合详解6:TreeMap和红黑树

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  7. Java集合详解5:深入理解LinkedHashMap和LRU缓存

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  8. Java虚拟机详解----JVM常见问题总结

    [正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾 ...

  9. Java集合排序及java集合类详解

    Java集合排序及java集合类详解 (Collection, List, Set, Map) 摘要内容 集合是Java里面最常用的,也是最重要的一部分.能够用好集合和理解好集合对于做Java程序的开 ...

最新文章

  1. 我的博士之路(壮根美颜-康亚龙):五年读博路,苦熬曙光明
  2. Python高级网络编程系列之第二篇
  3. python验证中心极限定理_我竟然混进了Python高级圈子!
  4. 富贵包这种常见颈椎病怎么改善?
  5. 搭建基于spring MVC框架 + RESTful架构风格技术总结
  6. java 正则表达式 开头_如何在Java中修复表达式的非法开头
  7. JavaScript eval() 函数,计算某个字符串,并执行其中的的 JavaScript 代码。
  8. C# 文本操作类 Trim() 和Replace()的用法小例子
  9. 2020-10-13 Comsol学习1
  10. 数据时代,大数据未来的发展趋势主要有哪些?
  11. Word去除多余的页眉
  12. 【微信小程序】使用 Cryptojs 解密微信绑定手机号码
  13. SpringBoot的幕后推手,分布式架构演进+相关笔记参考
  14. php excel下载打不开了,php下载excel无法打开的解决方法_PHP教程
  15. Highest Scoring Word
  16. ArrayList的实现原理以及实现线程安全
  17. shell基础---exit用法
  18. 关于选择性起始位点的新方法之SEASTAR: systematic evaluation of alternative transcription start sites in RNA...
  19. 删除在Godaddy注册的域名,申请退款的全过程
  20. 使div水平居中的方式

热门文章

  1. wireshark数据包分析实战
  2. Python命名空间和作用域
  3. 通过调用门进行控制转移 ——《x86汇编语言:从实模式到保护模式》读书笔记29
  4. contiki源码阅读之mmem.c
  5. DM8168心得之SD卡快速分区制作
  6. SecureCRT 或者 超级终端 始终无法ping通主机
  7. Android PC投屏简单尝试—最终章1
  8. 如何使用区块链技术进行项目开发
  9. 详解DPoS共识算法
  10. python爬虫从入门到放弃(一)之初识爬虫