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)