【小笔记】从源码看jdk1.8之前的HashMap为什么会产生死锁

众所周知HashMap不是一个线程安全的Map,并发情况下可能会出现数据丢失,除此之外,JDK1.8之前的HashMap在并发场景下还可能产生死锁。

JDK1.8之前为什么会产生死锁:

想要了解产生死锁的原因是首先要了解JDK1.8之前的HashMap扩容原理,因为死锁正是由多个线程同时进行扩容操作导致的,HashMap底层容器为一个Entry[]数组,扩容就是建立一个目标容量的新数组,并将元素重新放到新数组内,并将新数组赋值为容器即完成了扩容,产生死锁的关键就在于从旧数组转移元素到新数组,JDK1.8之前这个过程通过transfer方法实现:

// newTable即为新容器,rehash为是否需要重新计算hash值,无需关注
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; // 记录next
            if (rehash) { // 必要时重新计算hash
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity); // 算出新的下标
            e.next = newTable[i]; // 注意!!! 将新容器的i处节点赋值到当前元素的next上
            newTable[i] = e; // 当前元素成为新的头节点
            e = next; // 继续下一个节点
        }
    }
}

JDK1.8之前,HashMap底层是通过数组与链表实现的,JDK1.8才引入红黑树的概念。因此JDK1.7转移源码只针对链表进行操作了:
关键在于e.next = newTable[i];newTable[i] = e;e = next;这最后三行源码。这三行源码会使得引用关系逆序:假设扩容之前i处有元素A->B->C,扩容之后A,B,C三个元素依然在i处。此时遍历A->B->C的过程为:
HashMap迁移过程

可以看到迁移之后的引用关系产生的逆转,那么如果线程T1进行put操作,发现容量不足执行扩容操作,在未扩容时T2也进行put操作,发现容量不足同时进入扩容流程,是不是会发生这种情况呢:
HashMap死锁产生过程
至此死锁产生…

JDK1.8的优化:

JDK1.8 HashMap除了引入了红黑树以加快检索速度外,对于扩容时链表元素的转移也进行了优化避免了死锁问题:
链表元素转移关键代码:

do {
	next = e.next;
	if ((e.hash & oldCap) == 0) {	// 为0说明这个元素在原位,即为低位
		if (loTail == null)
			loHead = e;	// 记录低位抬头
		else
			loTail.next = e;	// 链接到loHead 所在的链表上
		loTail = e;	// 记录尾部
	}
	else {	// 不为为0说明这个元素在j + oldCap,即为高位
		if (hiTail == null)
			hiHead = e;	// 记录高位抬头
		else
			hiTail.next = e;// 链接到hiTail所在的链表上
		hiTail = e;	// 记录尾部
	}
} while ((e = next) != null);
if (loTail != null) {	// loTail 不为空说明低位有值
	loTail.next = null;	// 断开loTail.next的链接,因为loTail为尾部,若是loTail.next不为空,那么他会在高位
	newTab[j] = loHead;	// 赋值到新容器的对应位置
}
if (hiTail != null) {	// hiTail 不为空说明高位有值
	hiTail.next = null; // 断开hiTail.next的链接,因为hiTail为尾部,若是hiTail.next不为空,那么他会在低位
	newTab[j + oldCap] = hiHead;// 赋值到新容器的对应位置
}

JDK1.8通过记录头尾双节点的方式,保证了同位元素引用顺序保持不变,以此避免了元素引用逆序所导致的并发死锁问题。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值