概述
在hashMap中,当插入的值到达一定的量的时候,hashMap就会进行rehash,进行扩容,那么在扩容的时候就会发生死锁。
扩容条件
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
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++;
addEntry(hash, key, value, i);
return null;
}
复制代码
以上为新增元素的put方法,如果新增的元素通过hash计算的索引,在table数组中不存在,那么就会调用addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
复制代码
如果size大于threshold的值(为初始化的值),则会进行扩容,扩容的大小必须是2的倍数,因为在进行计算索引的时候,会进行位运算
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
复制代码
根据newCapacity创建新的Entry,并做为参数传送给transfer方法,transfer方法,就是把原有table中entry的数据重新进行hash计算,并保存至newTable中,然后table=newTable,完成扩容,那么发送死锁的地方就发送在transfer方法中
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
复制代码
通过前面的介绍,终于到了关键时刻了,下面看看怎么发送的死锁。
假如:table.length=2,值为3,7,5,排列如下
扩容后:table.length=4,排列如下
但是这个以上排列只是不发送死锁的情况,如果在高并发下场景,会出现不一样的结果。
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
复制代码
假如: 有线程A、线程B
Entry<K,V> next = e.next;
复制代码
当B第一次执行完以下代码,进行了挂起,那么:e=3,next=7,在B挂起的时,线程A完成了transfer的扩容,那么7.next=3,3.next=null,扩容后如下图:
接着线程B继续执行,计算e=3的index为3,完成第一次循环:
接着进行第二次循环,此时:e=7,next=3
为啥next=3呢?因为在线程A完成扩容后,7的next为3,那么在执行到 Entry<K,V> next = e.next时,所以next=3
接着进行第三次循环,此时:e=3,next=null,而当执行到
==e.next = newTable[i]== 语句时,坑爹的事情发生了,形成闭环了,如下图:
注意此时还没有发送死锁,死锁是在获取元素时产生的,如调用一个不存在的元素,并且index是3,那么恭喜你死锁发生了。