【HashMap】HashMap多线程下的死循环问题及JDK8版本的修复

背景

想要记录一下关于jdk下的 hashmap 存在的一些问题:
1、许多同学都知道JDK下的 HashMap 是线程不安全的,但究竟是怎样个不安全法,在多线程下会出现怎样的问题?其中原因是什么?

多线程下HashMap可能会出现的问题

1、多线程put操作后,可能会导致元素丢失
2、往里面put元素的时候,可能会产生闭环的链表,get的时候会产生死循环(jdk8已经修复)

问题1,也不仅是 hashmap,在多线程下操作同个对象,也是很常见的了:
线程1 和 线程2 同时put值,并且发生hash 碰撞,同个hash槽本是链表,因为多个线程同时put给链表的第一个位置,后元素把前元素覆盖掉,导致元素丢失(值被改变)。

问题2:死循环问题

出现死循环是要满足几个条件的,多个线程同时往 map 里面 put 元素,并且因为 容量触发阈值,进行 resize() 进行扩容,在扩容过程中把 table[] 扩大两倍,
resize() 方法里面会把 旧table 里面的值迁移到新 table 里面去(并且,会把元素里面的值反序,也正是因为会把链表反序,才会出现死循环的问题,如果不反序,也就会少了个问题,JDK8在元素较多时,将链表转成树,也是解决了这个问题吧)

再贴一下代码

HashMap的rehash源代码(JDK8之前的)

// Put一个Key,Value对到Hash表中:

public V put(K key, V value)
{
	......
	//算Hash值
	int hash = hash(key.hashCode());
	int i = indexFor(hash, table.length);
	//如果该key已被插入,则替换掉旧的value (链接操作)
	for (Entry<K,V> e = table[i]; e != null; e = e.next) {
		Object k;
		if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
			V oldValue = e.value;
			e.value = value;
			e.recordAccess(this);
			return oldValue;
		}
	}
	modCount++;
	//该key不存在,需要增加一个结点
	addEntry(hash, key, value, i);
	return null;
}


// 检查容量是否超标
void addEntry(int hash, K key, V value, int bucketIndex)
{
	Entry<K,V> e = table[bucketIndex];
	table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
	//查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resize
	if (size++ >= threshold)
		resize(2 * table.length);
} 


// 新建一个更大尺寸的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);
}

// 把table的元素填充到 newTable
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);
		}
	}
} 
从这个方法,可以看到,其实它会把链表反一下(如果有jdk7及之前的,可以跟一下就更容易看了)

关于这个问题,其实是发生在 JDK8版本之前才会的,JDK8及之后就修复了这个问题了,所以。。。知道就行吧。
但多线程下,即使不会出现死循环问题,但还是建议使用 ConcurrentHashMap 吧。

关于死循环问题,可参考: https://coolshell.cn/articles/9606.html
说得更清楚些,里面附了一些图解,有助理解。

(完毕~)

posted @ 2023-02-23 14:28  aaacarrot  阅读(72)  评论(0编辑  收藏  举报