背景
想要记录一下关于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
说得更清楚些,里面附了一些图解,有助理解。
(完毕~)
标签:hash,HashMap,int,JDK8,key,Entry,table,多线程 From: https://www.cnblogs.com/aaacarrot/p/17147858.html