千家信息网

HashMap是线程不安全的体现有哪些

发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,这篇文章主要讲解了"HashMap是线程不安全的体现有哪些",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"HashMap是线程不安全的体现有哪些"吧!1
千家信息网最后更新 2025年01月19日HashMap是线程不安全的体现有哪些

这篇文章主要讲解了"HashMap是线程不安全的体现有哪些",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"HashMap是线程不安全的体现有哪些"吧!

1.jdk1.7中的HashMap

在jdk1.8中对HashMap做了很多优化,这里先分析在jdk1.7中的问题,相信大家都知道在jdk1.7多线程环境下HashMap容易出现死循环,这里我们先用代码来模拟出现死循环的情况:

public class HashMapTest {      public static void main(String[] args) {          HashMapThread thread0 = new HashMapThread();          HashMapThread thread1 = new HashMapThread();          HashMapThread thread2 = new HashMapThread();          HashMapThread thread3 = new HashMapThread();          HashMapThread thread4 = new HashMapThread();          thread0.start();          thread1.start();          thread2.start();          thread3.start();          thread4.start();      }  }  class HashMapThread extends Thread {      private static AtomicInteger ai = new AtomicInteger();      private static Map map = new HashMap<>();      @Override      public void run() {          while (ai.get() < 1000000) {              map.put(ai.get(), ai.get());              ai.incrementAndGet();          }      }  }

上述代码比较简单,就是开多个线程不断进行put操作,并且HashMap与AtomicInteger都是全局共享的。在多运行几次该代码后,出现如下死循环情形:

其中有几次还会出现数组越界的情况:

这里我们着重分析为什么会出现死循环的情况,通过jps和jstack命名查看死循环情况,结果如下:

从堆栈信息中可以看到出现死循环的位置,通过该信息可明确知道死循环发生在HashMap的扩容函数中,根源在transfer函数中,jdk1.7中HashMap的transfer函数如下:

void transfer(Entry[] newTable, boolean rehash) {          int newCapacity = newTable.length;          for (Entry e : table) {              while(null != e) {                  Entry next = e.next;                  if (rehash) {                      e.hash = null == e.key ? 0 : hash(e.key);                  }                  int i = indexFor(e.hash, newCapacity);                  e.next = newTable[i];                  newTable[i] = e;                  e = next;              }          }     }

总结下该函数的主要作用:

在对table进行扩容到newTable后,需要将原来数据转移到newTable中,注意10-12行代码,这里可以看出在转移元素的过程中,使用的是头插法,也就是链表的顺序会翻转,这里也是形成死循环的关键点。下面进行详细分析。

1.1 扩容造成死循环分析过程

前提条件:

这里假设

  1. 鸿蒙官方战略合作共建--HarmonyOS技术社区

  2. hash算法为简单的用key mod链表的大小。

  3. 最开始hash表size=2,key=3,7,5,则都在table[1]中。

  4. 然后进行resize,使size变成4。

未resize前的数据结构如下:

如果在单线程环境下,最后的结果如下:

这里的转移过程,不再进行详述,只要理解transfer函数在做什么,其转移过程以及如何对链表进行反转应该不难。

然后在多线程环境下,假设有两个线程A和B都在进行put操作。线程A在执行到transfer函数中第11行代码处挂起,因为该函数在这里分析的地位非常重要,因此再次贴出来。

此时线程A中运行结果如下:

线程A挂起后,此时线程B正常执行,并完成resize操作,结果如下:

这里需要特别注意的点:由于线程B已经执行完毕,根据Java内存模型,现在newTable和table中的Entry都是主存中最新值:7.next=3,3.next=null。

此时切换到线程A上,在线程A挂起时内存中值如下:e=3,next=7,newTable[3]=null,代码执行过程如下:

newTable[3]=e ----> newTable[3]=3  e=next ----> e=7

此时结果如下:

继续循环:

e=7  next=e.next ----> next=3【从主存中取值】  e.next=newTable[3] ----> e.next=3【从主存中取值】  newTable[3]=e ----> newTable[3]=7  e=next ----> e=3

结果如下:

再次进行循环:

e=3  next=e.next ----> next=null  e.next=newTable[3] ----> e.next=7 即:3.next=7  newTable[3]=e ----> newTable[3]=3  e=next ----> e=null

注意此次循环:e.next=7,而在上次循环中7.next=3,出现环形链表,并且此时e=null循环结束。

结果如下:

在后续操作中只要涉及轮询hashmap的数据结构,就会在这里发生死循环,造成悲剧。

1.2 扩容造成数据丢失分析过程

遵照上述分析过程,初始时:

线程A和线程B进行put操作,同样线程A挂起:

此时线程A的运行结果如下:

此时线程B已获得CPU时间片,并完成resize操作:

同样注意由于线程B执行完成,newTable和table都为最新值:5.next=null。

此时切换到线程A,在线程A挂起时:e=7,next=5,newTable[3]=null。

执行newtable[i]=e,就将7放在了table[3]的位置,此时next=5。接着进行下一次循环:

e=5  next=e.next ----> next=null,从主存中取值  e.next=newTable[1] ----> e.next=5,从主存中取值  newTable[1]=e ----> newTable[1]=5  e=next ----> e=null

将5放置在table[1]位置,此时e=null循环结束,3元素丢失,并形成环形链表。并在后续操作hashmap时造成死循环。

2.jdk1.8中HashMap

在jdk1.8中对HashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程的情况下仍然不安全,这里我们看jdk1.8中HashMap的put操作源码:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                     boolean evict) {          Node[] tab; Node p; int n, i;          if ((tab = table) == null || (n = tab.length) == 0)              n = (tab = resize()).length;          if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素              tab[i] = newNode(hash, key, value, null);          else {              Node e; K k;              if (p.hash == hash &&                  ((k = p.key) == key || (key != null && key.equals(k))))                  e = p;              else if (p instanceof TreeNode)                  e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);              else {                  for (int binCount = 0; ; ++binCount) {                      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;      }

这是jdk1.8中HashMap中put操作的主函数, 注意第6行代码,如果没有hash碰撞则会直接插入元素。如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,所以这线程A、B都会进入第6行代码中。

假设一种情况,线程A进入后还未进行数据插入时挂起,而线程b正常执行,从而正常插入数据,然后线程a获取cpu时间片,此时线程A不用再进行hash判断了,问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。

这里只是简要分析下jdk1.8中HashMap出现的线程不安全问题的体现,后续将会对java的集合框架进行总结,到时再进行具体分析。

感谢各位的阅读,以上就是"HashMap是线程不安全的体现有哪些"的内容了,经过本文的学习后,相信大家对HashMap是线程不安全的体现有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

线程 循环 数据 分析 代码 函数 情况 结果 安全 过程 主存 位置 元素 问题 环境 环形 学习 碰撞 运行 信息 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 两台服务器能用一个域名吗 世界互联网领先的科技成果 创世神模组导入服务器 武汉铂嵘软件开发有限公司 tomcat 服务器管理 单位用的金蝶服务器很卡 云服务器的安全保障 网络安全服务项目的资质要求 服务器安全狗访问验证 诺顿网络安全专业版破解版 哈尔滨国家网络安全竞赛答案 山西软件开发招聘 网络技术力量简介 山东阿帕网络技术怎么样 谁是网络安全重中之重 国家数据库中心糯稻品种龙糯三号 网络安全法是几几年实施的 广西crm软件开发服务商 工信局网络安全宣传页 软件开发有哪些语音 安徽专业软件开发服务价钱 重庆泰多互联网科技有限公司 小明为了公司的服务器安全 秀逗互联网科技有限公司 数据库系统安全级别 网络安全发展趋势不好的部分 网络安全设计及风险评估 数据库设计与开发书籍 孤岛危机数据库 主要数据库安全性方法有哪些
0