HashMap并發(fā)情況下可引發(fā)的問題

1)put數(shù)據(jù),覆蓋導(dǎo)致丟失

假設(shè)2個線程:1)同時put數(shù)據(jù) 2)hash后都落在table[I] 3)按如下順序執(zhí)行createEntry代碼段,最后map的數(shù)據(jù)如圖狀態(tài)1,thread2的put丟失。

timeline thread1 thread2
1 Entry<K,V> e = table[bucketIndex];
2 Entry<K,V> e = table[bucketIndex];
3 table[bucketIndex] = new Entry<>(hash, key, value, e);
4 table[bucketIndex] = new Entry<>(hash, key, value, e);
put數(shù)據(jù)覆蓋丟失
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

2)resize時,get返回null

假設(shè)2個線程:1)thread1 put數(shù)據(jù)觸發(fā)resize 2)thead2 get數(shù)據(jù),hash后都落在table[I] 3)且按如下順序執(zhí)行代碼段,因為table[I]節(jié)點node.next==null,無法遍歷鏈表,get返回null。

timeline thread1 thread2
1 while(null != e) {……}
只執(zhí)行了一個循環(huán),table變?yōu)閠able'
2 for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {……}
table[I]節(jié)點e,e.next == null
get返回null
/**
     * Transfers all entries from current table to newTable.
     */
    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;
            }
        }
    }

/**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for the key.
     */
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

3)resize時,put導(dǎo)致數(shù)據(jù)丟失

假設(shè)2個線程:1)thread1 put觸發(fā)resize 2)thread resize未完成時,thead2 put又觸發(fā)resize 3)且按如下順序執(zhí)行代碼段,最終table狀態(tài)為newtable‘導(dǎo)致部分之前put的數(shù)據(jù)丟失。

timeline thread1 thread2
1 transfer(newTable, initHashSeedAsNeeded(newCapacity));
如圖newtable
2 transfer(newTable, initHashSeedAsNeeded(newCapacity));
如圖newtable’
3 table = newTable;
4 table = newTable;
同時resize
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);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    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;
            }
        }
    }

4)resize死循環(huán)

可以看酷 殼 – CoolShell 的這篇文章:JAVA HASHMAP的死循環(huán)

注:
以上代碼基于jdk1.7
IntelliJ IDEA 多線程debug

\color{red}{少看文章 多讀代碼?。?!}
\color{red}{少看文章 多想代碼?。?!}
\color{red}{少看文章 多寫代碼!??!}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容