HashMap非线程安全的原因

1,在插入数据的时候,后完成操作的线程会将先完成操作的线程的结果给替换掉。

2,当多个线程同时进来,检测到总数量超过门限值的时候同时调用resize操作,结果只有最后一个线程生成的新数组被赋给table变量

3,hashmap是fail-fast机制,不允许在遍历的同时对集合进行修改,否则会出现ConcurrentModifiedException

 

 

 

1,向HashMap中插入数据的时候

//向HashMap中添加Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length); //扩容2倍
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}
//创建一个Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];//先把table中该位置原来的Entry保存
    //在table中该位置新建一个Entry,将原来的Entry挂到该Entry的next
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    //所以table中的每个位置永远只保存一个最新加进来的Entry,其他Entry是一个挂一个,这样挂上去的
    size++;
}

现在如果A线程和B线程同时进入addEntry(),然后计算出了相同的哈希值对应了相同的数组位置,因为此时该位置还没有数据,然后对一个数组位置调用createEntry(),两个线程会同时得到现在的头节点,然后A写入新的头节点之后,B也写入新的头节点,那B的写入操作就会覆盖A的写入操作,造成A的写入操作丢失。

2,HashMap扩容的时候

//用新的容量来给table扩容  
void resize(int newCapacity) {  
    Entry[] oldTable = table; //保存old table  
    int oldCapacity = oldTable.length; //保存old capacity  
    // 如果旧的容量已经是系统默认最大容量了,那么将阈值设置成整形的最大值,退出    
    if (oldCapacity == MAXIMUM_CAPACITY) {  
        threshold = Integer.MAX_VALUE;  
        return;  
    }  

    //根据新的容量新建一个table  
    Entry[] newTable = new Entry[newCapacity];  
    //将table转换成newTable  
    transfer(newTable, initHashSeedAsNeeded(newCapacity));  
    table = newTable;  
    //设置阈值  
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);  
} 

当多个线程同时进来,检测到总数量超过门限值的时候同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被复制的table数组作为原始数组,这样也会有问题。

3,在删除HashMap中数据的时候

/根据指定的key删除Entry,返回对应的value  
public V remove(Object key) {  
    Entry<K,V> e = removeEntryForKey(key);  
    return (e == null ? null : e.value);  
}  

//根据指定的key,删除Entry,并返回对应的value  
final Entry<K,V> removeEntryForKey(Object key) {  
    if (size == 0) {  
        return null;  
    }  
    int hash = (key == null) ? 0 : hash(key);  
    int i = indexFor(hash, table.length);  
    Entry<K,V> prev = table[i];  
    Entry<K,V> e = prev;  

    while (e != null) {  
        Entry<K,V> next = e.next;  
        Object k;  
        if (e.hash == hash &&  
            ((k = e.key) == key || (key != null && key.equals(k)))) {  
            modCount++;  
            size--;  
            if (prev == e) //如果删除的是table中的第一项的引用  
                table[i] = next;//直接将第一项中的next的引用存入table[i]中  
            else  
                prev.next = next; //否则将table[i]中当前Entry的前一个Entry中的next置为当前Entry的next  
            e.recordRemoval(this);  
            return e;  
        }  
        prev = e;  
        e = next;  
    }  

    return e;  
}

一个线程得到了指定数组位置i并进入遍历,此时,另一个线程也在同样的位置已经删掉了i位置的那个数据了,然后第一个线程那边的数据也就没了,会出现ConcurrentModified Exception。(HahsMap是fail-fast)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值