在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,那么恭喜你死锁发生了。