一直以来都知道HashMap是线程不安全的,但是到底为什么线程不安全,在多线程操作情况下什么时候线程不安全?

让我们先来了解一下HashMap的底层存储结构,HashMap底层是一个Entry数组,一旦发生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突,Entry内部的变量:

  1. final Object key;
  2. Object value;
  3. Entry next;
  4. int hash;

通过Entry内部的next变量可以知道使用的是链表,这时候我们可以知道,如果多个线程,在某一时刻同时操作HashMap并执行put操作,而有大于两个key的hash值相同,如图中a1、a2,这个时候需要解决碰撞冲突,而解决冲突的办法上面已经说过,对于链表的结构在这里不再赘述,暂且不讨论是从链表头部插入还是从尾部初入,这个时候两个线程如果恰好都取到了对应位置的头结点e1,而最终的结果可想而知,a1、a2两个数据中势必会有一个会丢失,如图所示:

再来看下put方法

  1. public Object put(Object obj, Object obj1)
  2. {
  3. if(table == EMPTY_TABLE)
  4. inflateTable(threshold);
  5. if(obj == null)
  6. return putForNullKey(obj1);
  7. int i = hash(obj);
  8. int j = indexFor(i, table.length);
  9. for(Entry entry = table[j]; entry != null; entry = entry.next)
  10. {
  11. Object obj2;
  12. if(entry.hash == i && ((obj2 = entry.key) == obj || obj.equals(obj2)))
  13. {
  14. Object obj3 = entry.value;
  15. entry.value = obj1;
  16. entry.recordAccess(this);
  17. return obj3;
  18. }
  19. }
  20. modCount++;
  21. addEntry(i, obj, obj1, j);
  22. return null;
  23. }

put方法不是同步的,同时调用了addEntry方法:

  1. void addEntry(int i, Object obj, Object obj1, int j)
  2. {
  3. if(size >= threshold && null != table[j])
  4. {
  5. resize(2 * table.length);
  6. i = null == obj ? 0 : hash(obj);
  7. j = indexFor(i, table.length);
  8. }
  9. createEntry(i, obj, obj1, j);
  10. }

addEntry方法依然不是同步的,所以导致了线程不安全出现伤处问题,其他类似操作不再说明,源码一看便知,下面主要说一下另一个非常重要的知识点,同样也是HashMap非线程安全的原因,我们知道在HashMap存在扩容的情况,对应的方法为HashMap中的resize方法:

  1. void resize(int i)
  2. {
  3. Entry aentry[] = table;
  4. int j = aentry.length;
  5. if(j == 1073741824)
  6. {
  7. threshold = 2147483647;
  8. return;
  9. } else
  10. {
  11. Entry aentry1[] = new Entry[i];
  12. transfer(aentry1, initHashSeedAsNeeded(i));
  13. table = aentry1;
  14. threshold = (int)Math.min((float)i * loadFactor, 1.073742E+009F);
  15. return;
  16. }
  17. }

可以看到扩容方法也不是同步的,通过代码我们知道在扩容过程中,会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。

当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。

HashMap为什么线程不安全(hash碰撞与扩容导致)的更多相关文章

  1. hashMap的线程不安全

    hashMap是非线程安全的,表现在两种情况下: 1 扩容: t1线程对map进行扩容,此时t2线程来读取数据,原本要读取位置为2的元素,扩容后此元素位置未必是2,则出现读取错误数据. 2 hash碰 ...

  2. HashMap之Hash碰撞源码解析

    转自:https://blog.csdn.net/luo_da/article/details/77507315 https://www.cnblogs.com/tongxuping/p/827619 ...

  3. HashMap之Hash碰撞冲突解决方案及未来改进

    说明:参考网上的两篇文章做了简单的总结,以备后查(http://blogread.cn/it/article/7191?f=wb  ,http://it.deepinmind.com/%E6%80%A ...

  4. 从头认识java-15.7 Map(4)-介绍HashMap的工作原理-hash碰撞(常常作为面试题)

    这一章节我们来讨论一下hash碰撞. 1.什么是hash碰撞? 就是两个对象的key的hashcode是一样的,这个时候怎么get他的value呢? 答案是通过equals遍历table那个位置上面的 ...

  5. Java基础知识强化之集合框架笔记80:HashMap的线程不安全性的体现

    1. HashMap 的线程不安全性的体现: 主要是下面两方面: (1)多线程环境下,多个线程同时resize()时候,容易产生死锁现象.即:resize死循环 (2)如果在使用迭代器的过程中有其他线 ...

  6. 面试官:小伙子,你给我说一下HashMap 为什么线程不安全?

    前言:我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线程不安全主要体现在什么地方呢,本文将对该问题进行解密. 1.jdk1.7中的HashMap 在jdk1.8中对HashM ...

  7. HashMap 为什么线程不安全?

    作者:developer http://cnblogs.com/developer_chan/p/10450908.html 我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线 ...

  8. 为什么说 HashMap 是非线程安全的?

    我们在学习 HashMap 的时候,都知道 HashMap 是非线程安全的,同时我们知道 HashTable 是线程安全的,因为里面的方法使用了 synchronized 进行同步. 但是 HashM ...

  9. Java并发基础08. 造成HashMap非线程安全的原因

    在前面我的一篇总结(6. 线程范围内共享数据)文章中提到,为了数据能在线程范围内使用,我用了 HashMap 来存储不同线程中的数据,key 为当前线程,value 为当前线程中的数据.我取的时候根据 ...

随机推荐

  1. Selenium+Python浏览器调用:Firefox

    如何查看python selenium的API python -m pydoc -p  4567 说明: python -m pydoc表示打开pydoc模块,pydoc是查看python文档的首选工 ...

  2. [WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored (webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true')

    WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored (webxml attribu ...

  3. TS相关知识点

    数字电视的TS包和TS流的组成和功能 综合考虑几下几个因素: (1)包的长度不能过短,否则包头开销所占比例过大, 导致传输效率下降 (2)包的长度不能过长,否则在丢失同步的情况下恢复同步的 周期过长, ...

  4. UIViewController控制器的生命周期

    视图控制器就是用来管理iOS程序中的视图,默认一个UIViewController为我们提供了一个视图UIView  我们称为根视图 - (instancetype)init{ if (self = ...

  5. 003 assert简述

  6. B - A + B Again

    Description There must be many A + B problems in our HDOJ , now a new one is coming.         Give yo ...

  7. Hadoop学习之常用命令

    HADOOP基本操作命令 在这篇文章中,我们默认认为Hadoop环境已经由运维人员配置好直接可以使用. 假设Hadoop的安装目录HADOOP_HOME为/home/admin/hadoop. 启动与 ...

  8. ubuntu安装jira步骤

    背景:前些日子在原来的一台云主机上已经部署了一个jira系统,使用一段时间后发现jira占用很多的系统资源,导致主机上的其他服务无法正常工作,于是老大新注册了一个云主机专门用于运行jira,可见公司对 ...

  9. PHP_CURL请求教程, 内含简单粗暴curl

    //curl访问 //需要url或者data //返回的数组是JSON数据形式 function ppd_curl($url,$data = null){ //\Think\Log::record($ ...

  10. php的DOMDocument 如何判断xml是否存在某节点

    在做xml解释时,需要判断是否存在某个节点,查了手册函数,硬是没有发现那个函数有这个功能(如果你知道的话,请留言,博主感激涕零). 下面,就说下博主,经过N多博文,论坛查阅后,得到的一种方法(看到的博 ...