之前看过左耳朵耗子的博客,我自己再把这个问题重新整理一遍
1.HashMap表的数据结构
HashMap用一个指针数组来存储Entry, 按照Entry的key通过一定的规则(得到哈希值)来分散存储这些元素,假设摸一个Entry(B)
当按照这个规则计算出来的下标位置已有一个Entry(A),会将最新的Entry放在数组这个下标所在位置,同时
将B的next设置为A.
2.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; }
3.检查长度是否超过threshhold
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); }
4.如果新增一个元素后长度超过threshhold,则长度扩大一倍,并将原数组的元素转移到新数组
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); }
5.转移元素的代码
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;--------------------------------(1) int i = indexFor(e.hash, newCapacity);-------------------(2) e.next = newTable[i];------------------------------------(3) newTable[i] = e;-----------------------------------------(4) e = next;------------------------------------------------(5) } while (e != null); } } }
这个for循环在一定条件下,并发会出现无限循环。以文章一开始说的A B两个元素key的哈希值相等,对应数组下标i这个场景为例,
在两个线程(d1, d2)同时执行的情况下,
第一步:假设d1执行到代码行(1)时被挂起,此时 e为A next为B
第二步:然后线程d2执行,d2执行完毕后,newTable[i]上存储的元素为B, B的next为A
第三步:接下来,线程d1恢复调度,此时e为A,newTable[i]上存储的元素是B, 代码行(3)将A的next设置为B,
也就是说代码行(3)执行之后结合第二步的结果导致A的next为B,B的next为A, 这对这个循环意味着什么,会无穷无尽的执行下去,
最终导致系统资源耗光,CPU 100%的现象就出现了。