ConcurrentHashMapのは、無限ループに陥っ - なぜ?

ジョーカー:

いくつかの詳細な分析をしながらConcurrentHashMap、でも言っているインターネット上のブログの記事を見つけConcurrentHashMap、無限ループに陥る場合があります。

これは、この例を示します。私はこのコードを実行したとき - それは捕まってしまいました。

public class Test {
    public static void main(String[] args) throws Exception {
        Map<Long, Long> map = new ConcurrentHashMap<>();
        map.put(0L, 0L);
        map.put((1L << 32) + 1, 0L);
        for (long key : map.keySet()) {
            map.put(key, map.remove(key));
        }
    }
}

このデッドロックが発生した理由を説明してください。

Marco13:

他の人がすでに言ったように:それはデッドロックが、無限ループではありません。かかわらず、その、問題の核心(とタイトル)がある:これはなぜ起こるのでしょうか?

他の回答は、ここではあまり詳細には触れませんが、私はより良いだけでなく、これを理解することは興味がありました。例えば、あなたが行を変更

map.put((1L << 32) + 1, 0L);

map.put(1L, 0L);

それはないない動けなくなります。そして再び、質問がある理由


答えは:それは複雑です。

ConcurrentHashMap唯一の基本的な説明のコメントの230行で、コードのなんと6300本のラインと同時/コレクションフレームワークから最も複雑なクラスの一つ、ある概念の実装のを、なぜ魔法と読めないコードが実際に動作します以下は、かなり簡略化されているが、少なくとも説明しなければならない基本的な問題を。

まず第一に:によって返されたセットMap::keySetであるビュー内部状態に。そして、Javadocは言います:

戻り値このマップに含まれるキーのSetビューを。セットはマップによってバックアップされているので、マップへの変更はセットに反映され、およびその逆。セットの反復処理(反復子自身のremoveオペレーションを除く)中にマップが変更された場合、反復の結果は未定義れます。セットがサポートする要素の削除、[...]

(私が強調)

しかし、のJavadocはConcurrentHashMap::keySet言います:

戻り値このマップに含まれるキーのSetビューを。セットはマップによってバックアップされているので、マップへの変更はセットに反映され、およびその逆。セットがサポートする要素の削除、[...]

(それはないことに注意してくださいません未定義の動作に言及!)

繰り返し処理しながら、通常、マップを変更keySet投げるでしょうConcurrentModificationExceptionしかし、ConcurrentHashMapこれに対処することができます。あなたの場合のように-それは結果がまだ予想外であっても、一貫性のままで、まだ繰り返し処理することができます。


あなたが観測されていること挙動の理由に来ます:

ハッシュテーブル(またはハッシュマップ)は、基本的鍵からハッシュ値を計算し、エントリが追加されるべきであること、「バケツ」の指標として、このキーを使用して動作します。複数のキーが同じバケットにマッピングされている場合は、その後、バケット内のエントリは、通常のように管理されているリンクリスト同じことがのためのケースですConcurrentHashMap

ノードからなるテーブルの特に、「バケット」、 - - 次のプログラムは、テーブルの内部状態を印刷するために、いくつかの厄介な反射ハックを使用し、反復し、修正中:

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MapLoop
{
    public static void main(String[] args) throws Exception
    {
        runTestInfinite();
        runTestFinite();
    }

    private static void runTestInfinite() throws Exception
    {
        System.out.println("Running test with inifinite loop");

        Map<Long, Long> map = new ConcurrentHashMap<>();
        map.put(0L, 0L);
        map.put((1L << 32) + 1, 0L);

        int counter = 0;
        for (long key : map.keySet())
        {
            map.put(key, map.remove(key));

            System.out.println("Infinite, counter is "+counter);
            printTable(map);

            counter++;
            if (counter == 10)
            {
                System.out.println("Bailing out...");
                break;
            }
        }

        System.out.println("Running test with inifinite loop DONE");
    }

    private static void runTestFinite() throws Exception
    {
        System.out.println("Running test with finite loop");

        Map<Long, Long> map = new ConcurrentHashMap<>();
        map.put(0L, 0L);
        map.put(1L, 0L);

        int counter = 0;
        for (long key : map.keySet())
        {
            map.put(key, map.remove(key));

            System.out.println("Finite, counter is "+counter);
            printTable(map);

            counter++;
        }

        System.out.println("Running test with finite loop DONE");
    }


    private static void printTable(Map<Long, Long> map) throws Exception
    {
        // Hack, to illustrate the issue here:
        System.out.println("Table now: ");
        Field fTable = ConcurrentHashMap.class.getDeclaredField("table");
        fTable.setAccessible(true);
        Object t = fTable.get(map);
        int n = Array.getLength(t);
        for (int i = 0; i < n; i++)
        {
            Object node = Array.get(t, i);
            printNode(i, node);
        }
    }

    private static void printNode(int index, Object node) throws Exception
    {
        if (node == null)
        {
            System.out.println("at " + index + ": null");
            return;
        }
        // Hack, to illustrate the issue here:
        Class<?> c =
            Class.forName("java.util.concurrent.ConcurrentHashMap$Node");
        Field fHash = c.getDeclaredField("hash");
        fHash.setAccessible(true);
        Field fKey = c.getDeclaredField("key");
        fKey.setAccessible(true);
        Field fVal = c.getDeclaredField("val");
        fVal.setAccessible(true);
        Field fNext = c.getDeclaredField("next");
        fNext.setAccessible(true);

        System.out.println("  at " + index + ":");
        System.out.println("    hash " + fHash.getInt(node));
        System.out.println("    key  " + fKey.get(node));
        System.out.println("    val  " + fVal.get(node));
        System.out.println("    next " + fNext.get(node));
    }
}

以下のための出力runTestInfinite次のようにケース(冗長部分は省略)です。

Running test with infinite loop
Infinite, counter is 0
Table now: 
  at 0:
    hash 0
    key  4294967297
    val  0
    next 0=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 1
Table now: 
  at 0:
    hash 0
    key  0
    val  0
    next 4294967297=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 2
Table now: 
  at 0:
    hash 0
    key  4294967297
    val  0
    next 0=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 3
...
Infinite, counter is 9
...
Bailing out...
Running test with infinite loop DONE

一つは、キーのエントリがあることがわかります0し、キー4294967297(あなたです(1L << 32) + 1)常にバケット0で終わり、彼らはリンクリストとして維持されます。以上の繰り返しだから、keySetこの表で始まります:

Bucket   :   Contents
   0     :   0 --> 4294967297
   1     :   null
  ...    :   ...
  15     :   null

最初の反復では、キーを削除し0、基本的には、このいずれかにテーブルを回し、:

Bucket   :   Contents
   0     :   4294967297
   1     :   null
  ...    :   ...
  15     :   null

しかし、鍵は0直後に追加され、それが同じバケツで終わる4294967297-それは、リストの末尾に追加されるように:

Bucket   :   Contents
   0     :   4294967297 -> 0
   1     :   null
  ...    :   ...
  15     :   null

(これはによって示されるnext 0=0出力の一部)。

次の反復では、4294967297それが最初にあったのと同じ状態にテーブルをもたらし、除去され、再挿入します。

あなたの無限ループがどこから来ると、それはです。


それとは対照的に、用の出力runTestFiniteの場合はこれです:

Running test with finite loop
Finite, counter is 0
Table now: 
  at 0:
    hash 0
    key  0
    val  0
    next null
  at 1:
    hash 1
    key  1
    val  0
    next null
at 2: null
...
at 14: null
at 15: null
Finite, counter is 1
Table now: 
  at 0:
    hash 0
    key  0
    val  0
    next null
  at 1:
    hash 1
    key  1
    val  0
    next null
at 2: null
...
at 14: null
at 15: null
Running test with finite loop DONE

一つは、そのキーを見ることができます0し、1で終わる異なるバケット。そう関連する要素(すなわち最初の2つのバケット)を反復した後、そこに取り除かれるいかなるリンクされたリストはありません(および追加)の要素が追加されることができ、ループ終了

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=225979&siteId=1