在JDK7及其早期版本中HashMap在多线程环境下会发生扩容死锁的问题。

HashMap中在创建时默认会有16个桶,有一个默认加载因子0.75,如果Map中的Entry数量达到阈值(16*0.75)就会进行扩容,将原来的桶的数量扩展至原来的两倍,而在多线程环境下JDK7的HashMap会产生扩容死锁的问题。

下列方法是Map进行扩容的核心方法:

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, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
* 将所有Entry从当前表转移到新表。
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;//1
//计算Hash,然后计算新数组中的索引值
/*if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);*/
e.next = newTable[i];//2
newTable[i] = e;//3
e = next;//4
}
}
}

我们大致可以将Entry的重散列看成四步:

  • Entry<K,V> next = e.next;
  • e.next = newTable[i];
  • newTable[i] = e;
  • e = next;

我们分别将上面四行代码编号为:1、2、3、4

现在我们来重现多线程情况下扩容死锁的情况,为了方便演示,我们将原始Map的桶的数量定为4,扩容后容量翻倍变为8,原Map中有两个Entry需要重散列,分别为:A、B。

  1. 线程二执行完代码行1,阻塞:

  1. 线程一开始执行,并执行完代码行1:

  1. 线程一执行代码行2,假设我们在扩容时两个节点新索引值为5:
e.next = newTable[5];

由于此时newTable[5]==null所以就相当于e.next=null,也就是A.next=null。


4. 线程一执行代码行3:


5. 线程一执行代码行4:

  1. 线程一再次进入循环执行代码行一,此时线程一中next变量指向null:


7. 线程一执行代码行2:


8. 线程一执行代码行3:

  1. 线程一执行代码行4:


此时线程一的扩容工作已经进行完毕,我们开始回到线程二。

  1. 线程二执行代码行2:

    e.next = newTable[i];

由于线程二的newTable[i]=null,而e.next也就是A.next本来就为空,所以这句话在此时并未对结构造成实质影响。

  1. 线程二执行代码行3:


12. 线程二执行代码行4:

  1. 线程二再次循环执行代码行1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iE9kg19T-1573800259936)(…/images/29.png)]

  1. 线程二执行代码行2:

    本行代码对结构无影响

  1. 线程二执行代码行3:

  2. 线程二执行代码行4:

  1. 线程二执行代码行1:


18. 线程二执行代码行2(成环):

此时A、B两节点之间就会形成环形结构。

  1. 线程二执行代码行3:

  2. 线程二执行代码行4:


在JDK8中由于采取了不同的扩容机制,所以在JDK8中不会出现扩容死锁问题。但是这并不意味着JDK8的HashMap是线程安全的。

图解JDK7及其早期版本HashMap扩容死锁问题的更多相关文章

  1. JDK7与JDK8中HashMap的实现

    JDK7中的HashMap HashMap底层维护一个数组,数组中的每一项都是一个Entry transient Entry<K,V>[] table; 我们向 HashMap 中所放置的 ...

  2. 关于JDK1.8 HashMap扩容部分源码分析

    今天回顾hashmap源码的时候发现一个很有意思的地方,那就是jdk1.8在hashmap扩容上面的优化. 首先大家可能都知道,1.8比1.7多出了一个红黑树化的操作,当然在扩容的时候也要对红黑树进行 ...

  3. 将“早期版本的Windows”改名

    将“早期版本的Windows”改名,并修改系统等待时间 问题描述:       先装Windows XP,再装Windows 7,启动菜单会出现“早期版本的Windows”与“Windows 7”两个 ...

  4. 安装了SQL2005再安装SQL 2008R2,提示此计算机上安装了 Microsoft Visual Studio 2008 的早期版本和检查是否安装了 SQL Server 2005 Express 工具的解决方案

    工作电脑上安装了SQL 2005, 但是客户电脑上安装的是SQL 2008R2,有时候连接他们的库调试没法连接,很不方便.然后又安装了个SQL2008 R2,期间遇到这两个问题,网上搜索了一下收到了解 ...

  5. SQL SERVER安装提示“安装了 Microsoft Visual Studio 2008 的早期版本

    工作共遇到的问题记录: 安装Sql Server 2008 R2时提示错误:“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本.请在安装 SQL Server 2 ...

  6. 【转】预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)

    用VC++ 2008 编写C语言程序,编译出现错误: 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反) 解决方法: 建工程时 建立空项目 或者在项目设置里关闭预编 ...

  7. VS2005 MFC 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)

    当 Visual C++ 项目启用了预编译头 (Precompiled header) 功能时,如果项目中同时混合有 .c 和 .cpp 源文件,则可能收到 C1853 编译器错误:fatal err ...

  8. Java 垃圾回收机制(早期版本)

    Java 垃圾回收机制在我们普通理解来看,应该视为一种低优先级的后台进程来实现的,其实早期版本的Java虚拟机并非以这种方式实现的. 先从一种很简单的垃圾回收方式开始. 引用计数 引用计数是一种简单但 ...

  9. [转帖]Windows 10 部分早期版本已完全停止技术支持服务

    Windows 10 部分早期版本已完全停止技术支持服务 2019-4-12 01:27| 发布者: cjy__05| 查看: 10186| 评论: 47|来自: pcbeta 收藏分享 转帖来源:h ...

  10. 面试笔记--HashMap扩容机制

    转载请注明出处 http://www.cnblogs.com/yanzige/p/8392142.html 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. 存放新 ...

随机推荐

  1. 一图读懂HUAWEI HiAI Foundation

    作为华为端侧AI的创新开放平台,HMS Core的HUAWEI HiAI Foundation开放AI算力,助力AI应用高效开发,同时联合多领域打造AI生态,实现日调用600亿次的突破,助力AI生态繁 ...

  2. Qt通过UDP发送广播

    // x.h QUdpSocket* udp = nullptr; // UDP对象 void createUdpAndSendData(); // 创建UDP对象和发送广播数据 void dropU ...

  3. NL2SQL进阶系列(1):DB-GPT-Hub、SQLcoder、Text2SQL开源应用实践详解

    NL2SQL进阶系列(1):DB-GPT-Hub.SQLcoder.Text2SQL开源应用实践详解 NL2SQL基础系列(1):业界顶尖排行榜.权威测评数据集及LLM大模型(Spider vs BI ...

  4. c# aspose操作word文档

    背景 这个是一个操作word文档的插件 1.1插入图片 using Aspose.Words; using Aspose.Words.Drawing; using Aspose.Words.Rende ...

  5. Typora图床配置(Typora+PicGo+Github)

    Typora图床配置(Typora+PicGo+Github) 一.Github配置 登录github:https://github.com/ 新建仓库 生成私人令牌 Settings->Dev ...

  6. Java 应用压测性能问题定位经验分享

    简介: 问题千千万,但只要修练了足够深厚的内功,形成一套属于自己的排查问题思路和打法,再加上一套支撑问题排查的工具,凭借已有的经验还有偶发到来的那一丝丝灵感,相信所有的问题都会迎刃而解. 作者:凡勇 ...

  7. 开箱即用!Linux 内核首个原生支持,让你的容器体验飞起来!| 龙蜥技术

    简介: 本文将从 Nydus 架构回顾.RAFS v6 镜像格式和 EROFS over Fscache 按需加载技术三个角度来分别介绍这一技术的演变历程. 文/阿里云内核存储团队,龙蜥社区高性能存储 ...

  8. EventBridge 集成云服务实践

    ​简介:本篇文章主要向大家分享了通过 EventBridge 如何集成云产品事件源,如何集成云产品事件目标以及通过事件流如何集成消息产品. 作者:李凯(凯易) EvenBridge 集成概述 Even ...

  9. Let's Fluent:更顺滑的MyBatis

    简介: 只需瞅一眼Google Trends上全球Java界最热门的两款SQL映射框架近一年的对比数字,就不难了解其实力分布:在此领域,MyBatis早已占领东亚地区开发者市场,并以绝对优势稳居中国最 ...

  10. WPF 引用 UWP 控件 不打包为 MSIX 分发的方法

    按照微软的官方文档,大部分的文档都会说如果用了 XAML Islands 等技术的时候,需要新建一个打包项目,将 WPF 应用打包为 msix 等才可以进行分发和使用.但是实际上不打包也可以,此时可以 ...