博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HashMap为什么是线程不安全的
阅读量:3523 次
发布时间:2019-05-20

本文共 2881 字,大约阅读时间需要 9 分钟。

HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。

 

我们来分析一下多线程访问:

 1.在hashmap做put操作的时候会调用下面方法:

复制代码

// 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。          void addEntry(int hash, K key, V value, int bucketIndex) {              // 保存“bucketIndex”位置的值到“e”中              Entry
e = table[bucketIndex]; // 设置“bucketIndex”位置的元素为“新Entry”, // 设置“e”为“新Entry的下一个节点” table[bucketIndex] = new Entry
(hash, key, value, e); // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小 if (size++ >= threshold) resize(2 * table.length); }

复制代码

 在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失

2.删除键值对的代码

复制代码

      // 删除“键为key”的元素          final Entry
removeEntryForKey(Object key) { // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算 int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry
prev = table[i]; Entry
e = prev; // 删除链表中“键为key”的元素 // 本质是“删除单向链表中的节点” while (e != null) { Entry
next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }

复制代码

当多个线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改。

3.addEntry中当加入新的键值对后键值对总数量超过门限值的时候会调用一个resize操作,代码如下:

复制代码

// 重新调整HashMap的大小,newCapacity是调整后的容量          void resize(int newCapacity) {              Entry[] oldTable = table;              int oldCapacity = oldTable.length;             //如果就容量已经达到了最大值,则不能再扩容,直接返回            if (oldCapacity == MAXIMUM_CAPACITY) {                  threshold = Integer.MAX_VALUE;                  return;              }                   // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中,              // 然后,将“新HashMap”赋值给“旧HashMap”。              Entry[] newTable = new Entry[newCapacity];              transfer(newTable);              table = newTable;              threshold = (int)(newCapacity * loadFactor);          }

复制代码

 这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。

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

转载地址:http://gduhj.baihongyu.com/

你可能感兴趣的文章
Codeforces Round #536 (Div. 2) D. Lunar New Year and a Wander(基础图论)
查看>>
牛客寒假算法基础集训营6 A:出题(思维)+B:煤气灶(二分)+C:项链(简单贪心)+D:美食(模拟)
查看>>
牛客寒假算法基础集训营6 E:海啸(二维树状数组 or 前缀和 +容斥定理)
查看>>
G:区间或和(思维)
查看>>
牛客寒假算法基础集训营6 I:wzoi(stack的应用)
查看>>
牛客寒假算法基础集训营5 A:炫酷双截棍+G:炫酷数字(唯一分解定理+埃式筛法)+J:炫酷数学(快速幂)
查看>>
牛客寒假算法基础集训营5 I:炫酷镜子(模拟 or 记忆化搜索)+D:炫酷路途(贪心求最短路)
查看>>
C:小a与星际探索(线性基 or 搜索bfs or 背包dp)
查看>>
牛客寒假算法基础集训营1 D:小a与黄金街道(欧拉函数+快速幂)+G:小a的排列(思维)
查看>>
学习使我快乐学习使我升华只要你爱学习我们就是一辈子的好朋友
查看>>
牛客寒假算法基础集训营2 A:处女座的签到题(排序)+C:处女座的砝码(思维)+J:处女座的期末复习(贪心)
查看>>
牛客寒假算法基础集训营2 B : 处女座与cf (巨坑模拟题)
查看>>
牛客寒假算法基础集训营2 G:处女座与复读机(搜索dfs or dp)+H:处女座的测验(一)(质数+构造)
查看>>
codevs 1102:采药(记忆化搜索 or dp)+1144:守望者的逃离(记忆化搜索 or 贪心)
查看>>
牛客练习赛39 A:走方格 (思维)
查看>>
牛客练习赛39 B:选点 (dfs序+LIS)
查看>>
牛客练习赛39 C:流星雨 (概率dp+除法逆元)
查看>>
牛客练习赛39 D:动态连通块(并查集+bitset优化)
查看>>
牛客寒假算法基础集训营6 H:肥猪(贪心+枚举)
查看>>
牛客寒假算法基础集训营6 F:石头剪刀布(思维+递归)
查看>>