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

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

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

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

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

2.删除键值对的代码

<span style="font-size: 18px;">      </span>// 删除“键为key”的元素
final Entry<K,V> removeEntryForKey(Object key) {
// 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev; // 删除链表中“键为key”的元素
// 本质是“删除单向链表中的节点”
while (e != null) {
Entry<K,V> 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作为原始数组,这样也会有问题。

最新文章

  1. php 上传文件
  2. YII2如何修改默认控制器/方法
  3. 基于superagent 与 cheerio 的node简单爬虫
  4. 圆形imageview
  5. Mac下的eclipse按住ctrl点击无法查看类文件
  6. 中国区Windows Azure 提供的功能以及与国外的差异
  7. linux/windows系统oracle数据库简单冷备同步
  8. cx_Oracle使用方法二
  9. Linux企业级项目实践之网络爬虫(27)——多路IO复用
  10. iOS 使用UILocalizedIndexedCollation实现区域索引标题(Section Indexed Title)即拼音排序
  11. 网站压力测试工具之WebBench
  12. Git客户端(Windows系统)的使用(Putty)(转)
  13. kali切换字符界面模式和切换图形界面模式
  14. 创建一个servlet
  15. 菜鸟之旅——.NET垃圾回收机制
  16. unity零基础开始学习做游戏(二)让你的对象动起来
  17. 自学华为IoT物联网_05 能源工业物联网常见问题及解决方案
  18. 开启mac上印象笔记的代码块
  19. 视音频数据处理入门:AAC音频码流解析
  20. springboot报错Whitelabel Error Page

热门文章

  1. 借助nodejs解析加密字符串 node安装库较python方便
  2. 流畅python学习笔记:第十二章:子类化内置类型
  3. linux-shell脚本命令之grep
  4. Swift - 修改导航栏的样式(文字颜色,背景颜色,背景图片)
  5. spring cloud初识
  6. Canvas动画按钮
  7. 3D立方体旋转动画
  8. uboot 2013.01 代码简析(2)第一阶段初始化
  9. nginx+keepalived简单双机主从热备
  10. matlab程序计时