なぜ、HashMapのスレッドセーフ?

はじめに:私たちは、HashMapのは、スレッドセーフではありませんことをマルチスレッド環境でのすべてのノウハウが推奨されていませんが、それは主に安全な場所にそれを通すものの中に反映され、この記事では、問題を解読します。

1.jdk1.7のHashMapの

私たちはすべて我々が最初に死んで見えるシミュレートするためのコードを使用してマルチスレッド環境で発生しやすいjdk1.7無限ループをHashMapの知っていると信じて、でjdk1.7で問題の最初の分析、最適化の多くを行っjdk1.8のHashMapのでは、サイクルの状況:

public class HashMapTest {

    public static void main(String[] args) {
         HashMapThread thread0 = new HashMapThread();
         HashMapThread thread1 = new HashMapThread();
         HashMapThread thread2 = new HashMapThread();
         HashMapThread thread3 = new HashMapThread();
         HashMapThread thread4 = new HashMapThread();
         thread0.start();
         thread1.start();
         thread2.start();
         thread3.start();
         thread4.start();
     }
}

class HashMapThread extends Thread {
    private static AtomicInteger ai = new AtomicInteger();
    private static Map<Integer, Integer> map = new HashMap<>();

    @Override
    public void run() {
        while (ai.get() < 1000000) {
             map.put(ai.get(), ai.get());
             ai.incrementAndGet();
         }
     }
}

コードは、比較的簡単な操作を入れて連続する複数のスレッドを開くことです、とのAtomicIntegerとHashMapのは、グローバルに共有されています。無限ループシナリオは次のように実行に数回は、より多くのコードよりも表示されます。

クロスボーダーの数倍の配列が表示されますする状況があります。

ここでは、次のような結果があり、我々は無限ループの場合は、表示される理由の分析に焦点を当て、という名前のビュー死のサイクルを通じ状況とJPSはjstack:

情報が、HashMapの拡張機能で発生した死亡のサイクルのはっきり認識することができ、それを通してスタック位置無限ループから見ることができる、の根伝達関数 HashMapの伝達関数には、jdk1.7は次の通りであります:

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;
        }
    }
}

機能の主な役割を要約:

NEWTABLEの拡張のための表では、すなわち、リストの順序を逆に、転写要素の過程において、第1の補間法が使用されて見ることができるオリジナルNEWTABLE、通知線10-12にデータを転送する必要があろう、ここで重要な点は無限ループを形成することです。以下の詳細な分析。

無限ループ解析で得られた1.1拡張

前提条件:

これは、想定されます

キーMODリストのサイズと#1.hash単純なアルゴリズム。

#2.まずハッシュテーブルサイズ= 2、キー= 3,7,5、テーブルにある[1]です。

#3は、そのサイズSO 4に、リサイズ。

リサイズ前のデータなし構造は、以下ません。

シングルスレッド環境での場合は、次のように、最終的な結果は以下のとおりです。

転送プロセスは、ここでは、あなたが反転リストへの転送処理とどのように難しいことではありませんした伝達関数を理解すれば何をすべきかを詳細に説明しません。

そして、マルチスレッド環境では、2つのスレッドAとBは、put操作中にあることを前提としています。分析位置にある関数は、ここで非常に重要であるため、ハングアップするためのコードの最初の11行の伝達関数の実装でスレッドが、そう再び掲載します。

結果は以下の通りである。この場合、スレッド:

中断されたスレッドAの後、次いでBが正常に実行スレッド、およびサイズ変更操作が完了し、結果は次の通りであります:

スレッドBが実装されているのでJavaのメモリモデルによると、現在はメインメモリ内NEWTABLEエントリテーブルは、最新の値です:それはポイントに特に注意を払う必要があります7.next = 3,3.next = nullを。

次のようにスレッドAに切り替え、メモリAの値がスレッドを中断このとき、E = 3、次= 7、NEWTABLE [3] = nullを次のように、コードが実行されるプロセスです。

newTable[3]=e ----> newTable[3]=3
e=next ----> e=7

この場合、次の結果:

サイクルを続行:

e=7
next=e.next ----> next=3【从主存中取值】
e.next=newTable[3] ----> e.next=3【从主存中取值】
newTable[3]=e ----> newTable[3]=7
e=next ----> e=3

結果は以下の通りであります:

再循環さ:

e=3
next=e.next ----> next=null
e.next=newTable[3] ----> e.next=7 即:3.next=7
newTable[3]=e ----> newTable[3]=3
e=next ----> e=null

なおサイクル:e.next = 7、および= 3最後のサイクル7.nextに、円形のリンクリスト、及びこの時点でE =ヌルサイクルの終了を生じます。

結果は以下の通りであります:

ポーリングハッシュマップのデータ構造限り、無限ループは悲劇で、後続の操作で、ここで発生します。

データ解析処理の損失をもたらす1.2拡張

最初に、上記の分析に従ってください:

:スレッドAとスレッドBは、操作、同じスレッドA保留を入れています

結果は以下の通りである時点で実行中のスレッド:

このとき、スレッドBにはCPUのタイムスライスを受けていない、とサイズ変更操作を完了しました。

:またによる完了スレッドBの実装に、NEWTABLEとテーブルが最新の値であることに注意5.next = nullを

この時点で、スレッドAに切り替え、スレッドのハングA:E = 7、= 5次に、NEWTABLE =ヌル。[3]。

NEWTABLE [I] = E、** 7は、テーブルの上に配置される[3] **次= 5ポジションを行います。そして、次のサイクル:

e=5
next=e.next ----> next=null,从主存中取值
e.next=newTable[1] ----> e.next=5,从主存中取值
newTable[1]=e ----> newTable[1]=5
e=next ----> e=null

5テーブルに配置されます[1] E =ヌルサイクルが終了した位置、3損失素子とが形成されている円形のリンクリストをそして、その後の操作のハッシュマップ中に無限ループを引き起こします。

2.jdk1.8中のHashMap

HashMapのにjdk1.8では状況は、円形のリンクリストを表示されますが、マルチスレッドの場合は、まだ安全ではないないように、衝突は、もはや第1の補間モードを使用しますが、直接、リストの末尾に、ハッシュを発生せず、最適化されましたここでは、HashMapのは、動作jdk1.8ソースに入れて参照してください。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; 
        K k;
    if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
        e = p;
    else if (p instanceof TreeNode)
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
        for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
                p.next = newNode(hash, key, value, null);
                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

これは、HashMapのはjdk1.8の主な機能は、運転中に存在する場合、コードの行6は、何のハッシュ衝突が要素に直接発生していないことに注意を入れています。スレッドAおよびスレッドBが動作を入れている場合だけ、ハッシュ値のような2つの異なるデータ、位置データは、スレッドAは、Bはコード線6に入るように、ヌルです。状況を想定、スレッドAが保留データに挿入されているが行われていない、およびスレッドBは、通常、データが挿入されるように、正常に実行され、CPUのタイムスライス取得スレッドAは、問題、さらなる決意ハッシュスレッドありません:スレッドBをスレッドにデータを挿入するカバー、スレッドセーフの発生。

ここでは簡単な分析が低いjdk1.8は、フレームワークが要約されているJavaコレクションにフォローアップし、その後具体的な分析になり、HashMapのスレッド不安に表示されます反映しています。

概要

まずHashMapのは、スレッドセーフではない、主に反映さ:

jdk1.7で#1は、マルチスレッド環境では、エンドレスチェーンやデータの損失拡大になります。

jdk1.8で#2は、マルチスレッド環境では、例のデータカバレッジが行われます。

公開された50元の記事 ウォンの賞賛1706 ビュー222万+

おすすめ

転載: blog.csdn.net/zl1zl2zl3/article/details/105233273