从源码分析JDK8对HashMap死循环的修复

引言

上一篇咱们分析了HashMap的put和get方法,大家应该对HashMap有个简单的了解,如果不了解得可以看上一篇文章。本文分析的重点是resize扩容方法,因为JDK8在这段代码上面对JDK7做了修改,现在我们一起来看看具体做了哪些修改,以及为什么要这么修改。

1、分析JDK7源码

新建一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中

void resize(int newCapacity)
{
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    ......
    //创建一个新的Hash Table
    Entry[] newTable = new Entry[newCapacity];
    //将Old Hash Table上的数据迁移到New Hash Table上
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}

下面是迁移的代码:

void transfer(Entry[] newTable)
{
    Entry[] src = table;
    int newCapacity = newTable.length;
    //下面这段代码的意思是:
    //  从OldTable里摘一个元素出来,然后放到NewTable中
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

我们都知道HashMap是非线程安全的,上面这段代码在多线程的情况下会导致HashMap死循环,我们来演示下这个过程,最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,现在要进行扩容了,假设我们有两个线程,线程二执行完成了:
在这里插入图片描述
线程一现在开始执行,先是执行 newTalbe[i] = e,然后是e = next,导致了e指向了key(7),而下一次循环的next = e.next导致了next指向了key(3)
在这里插入图片描述
再看下一流程,也正常
在这里插入图片描述
环形链接出现,e.next = newTable[i] 导致 key(3).next 指向了 key(7)
注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
在这里插入图片描述

2、分析JDK8源码

由于resize的方法比较长,我先说下具体的思路,然后再贴源码,大家就更加清楚了:

  • 如果table == null, 则初始化threshold值, 生成空table返回
  • 如果table不为空, 需要重新计算table的长度, newCap= oldCap<< 1(扩容为原来的2倍, 如果原oldCap已经到了上限, 则newCap= oldCap)
  • 遍历oldTable,首节点不为空则进入下一步,为空则本次循环结束。
  • 无后续节点, 重新计算hash位, 并赋值,本次循环结束
  • 当前是红黑树, 走红黑树的重定位
  • 当前是链表, JAVA7时还需要重新计算hash位, 但是JAVA8做了优化, 通过(e.hash & oldCap) == 0来判断是否需要移位; 如果为真则在原位不动, 否则则需要移动到当前hash槽位 + oldCap的位置
final Node<K,V>[] resize() {
	Node<K,V>[] oldTab = table;
	int oldCap = (oldTab == null) ? 0 : oldTab.length;
	int oldThr = threshold;
	int newCap, newThr = 0;
	if (oldCap > 0) {
		if (oldCap >= MAXIMUM_CAPACITY) {
			threshold = Integer.MAX_VALUE;
			return oldTab;
		}
		else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
				 oldCap >= DEFAULT_INITIAL_CAPACITY)
			newThr = oldThr << 1; // double threshold
	}
	else if (oldThr > 0) // initial capacity was placed in threshold
		newCap = oldThr;
	else {               // zero initial threshold signifies using defaults
		newCap = DEFAULT_INITIAL_CAPACITY;
		newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
	}
	if (newThr == 0) {
		float ft = (float)newCap * loadFactor;
		newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
				  (int)ft : Integer.MAX_VALUE);
	}
	threshold = newThr;
	@SuppressWarnings({"rawtypes","unchecked"})
		Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	table = newTab;
	if (oldTab != null) {
		for (int j = 0; j < oldCap; ++j) {
			Node<K,V> e;
			if ((e = oldTab[j]) != null) {
				oldTab[j] = null;
				if (e.next == null)
					newTab[e.hash & (newCap - 1)] = e;
				else if (e instanceof TreeNode)
					((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
				else { // preserve order
					Node<K,V> loHead = null, loTail = null;
					Node<K,V> hiHead = null, hiTail = null;
					Node<K,V> next;
					do {
						next = e.next;
						if ((e.hash & oldCap) == 0) {
							if (loTail == null)
								loHead = e;
							else
								loTail.next = e;
							loTail = e;
						}
						else {
							if (hiTail == null)
								hiHead = e;
							else
								hiTail.next = e;
							hiTail = e;
						}
					} while ((e = next) != null);
					if (loTail != null) {
						loTail.next = null;
						newTab[j] = loHead;
					}
					if (hiTail != null) {
						hiTail.next = null;
						newTab[j + oldCap] = hiHead;
					}
				}
			}
		}
	}
	return newTab;
}

3、总结

  • JDK8对HashMap死循环的解决方法是:扩容后,新数组中的链表顺序依然与旧数组中的链表顺序保持一致。
  • JDK8虽然修复了死循环的bug,但是HashMap 还是非线程安全类,仍然会产生数据丢失等问题,线程安全类还是使用ConcurrentHashMap

结束语

本篇至此终于写完了,关于ConcurrentHashMap的介绍会晚一点写,下一篇的内容将会分析HashSet

如果本篇内容对你有所帮助,请随手点个赞,谢谢大家!

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/105731952