之前看过左耳朵耗子的博客,我自己再把这个问题重新整理一遍

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%的现象就出现了。

posted on 2015-03-23 16:38  xuqiyu  阅读(527)  评论(0编辑  收藏  举报