web-dev-qa-db-ja.com

ConcurrentSkipListMapはいつ使用する必要がありますか?

Javaでは、ConcurrentHashMapが優れたmultithreadingソリューションを提供します。次に、いつConcurrentSkipListMapを使用する必要がありますか?冗長性ですか?

これら2つの間のマルチスレッドの側面は一般的ですか?

70
DKSRathore

これら2つのクラスはいくつかの点で異なります。

ConcurrentHashMap は、契約の一部としての操作の実行時間を保証しません*。また、特定の負荷係数(大まかに、同時に変更するスレッドの数)を調整することもできます。

ConcurrentSkipListMap 、一方で、平均O(log(n))さまざまな操作でのパフォーマンスを保証します。並行処理のためのチューニングもサポートしていません。 ConcurrentSkipListMapには、ConcurrentHashMapにはない多くの操作があります:ceilingEntry/Key、floorEntry/Keyなどです。また、ソート順を維持します。費用)ConcurrentHashMapを使用していた場合。

基本的に、さまざまなユースケースに対してさまざまな実装が提供されます。迅速な単一キー/値ペアの追加と迅速な単一キー検索が必要な場合は、HashMapを使用します。より高速の順序走査が必要で、挿入に余分なコストがかかる場合は、SkipListMapを使用します。

*実装は、O(1)挿入/ルックアップ;再ハッシュを無視する)の一般的なハッシュマップの保証とほぼ一致すると予想しますが

67
Kevin Montrose

データ構造の定義については、 スキップリスト を参照してください。 ConcurrentSkipListMapは、マップをキーの自然な順序(または定義した他のキー順序)で保存します。したがって、HashMapよりもget/put/contains操作が遅くなりますが、これを相殺するために、SortedMapおよびNavigableMapインターフェースをサポートします。

14
Jim Ferrans

パフォーマンスの観点から、skipListがMapとして使用される場合、10〜20倍遅いようです。これが私のテストの結果です(Java 1.8.0_102-b14、win x32)

Benchmark                    Mode  Cnt  Score    Error  Units
MyBenchmark.hasMap_get       avgt    5  0.015 ?  0.001   s/op
MyBenchmark.hashMap_put      avgt    5  0.029 ?  0.004   s/op
MyBenchmark.skipListMap_get  avgt    5  0.312 ?  0.014   s/op
MyBenchmark.skipList_put     avgt    5  0.351 ?  0.007   s/op

そしてそれに加えて、お互いを比較することが本当に意味があるユースケース。これらのコレクションの両方を使用して、最後に最近使用されたアイテムのキャッシュの実装。現在、skipListの効率は、より疑わしいイベントのようです。

MyBenchmark.hashMap_put1000_lru      avgt    5  0.032 ?  0.001   s/op
MyBenchmark.skipListMap_put1000_lru  avgt    5  3.332 ?  0.124   s/op

JMHのコードは次のとおりです(Java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1

static final int nCycles = 50000;
static final int nRep = 10;
static final int dataSize = nCycles / 4;
static final List<String> data = new ArrayList<>(nCycles);
static final Map<String,String> hmap4get = new ConcurrentHashMap<>(3000, 0.5f, 10);
static final Map<String,String> smap4get = new ConcurrentSkipListMap<>();

static {
    // prepare data
    List<String> values = new ArrayList<>(dataSize);
    for( int i = 0; i < dataSize; i++ ) {
        values.add(UUID.randomUUID().toString());
    }
    // rehash data for all cycles
    for( int i = 0; i < nCycles; i++ ) {
        data.add(values.get((int)(Math.random() * dataSize)));
    }
    // rehash data for all cycles
    for( int i = 0; i < dataSize; i++ ) {
        String value = data.get((int)(Math.random() * dataSize));
        hmap4get.put(value, value);
        smap4get.put(value, value);
    }
}

@Benchmark
public void skipList_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void skipListMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            smap4get.get(key);
        }
    }
}

@Benchmark
public void hashMap_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void hasMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            hmap4get.get(key);
        }
    }
}

@Benchmark
public void skipListMap_put1000_lru() {
    int sizeLimit = 1000;

    for( int n = 0; n < nRep; n++ ) {
        ConcurrentSkipListMap<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && map.size() > sizeLimit ) {
                // not real lru, but i care only about performance here
                map.remove(map.firstKey());
            }
        }
    }
}

@Benchmark
public void hashMap_put1000_lru() {
    int sizeLimit = 1000;
    Queue<String> lru = new ArrayBlockingQueue<>(sizeLimit + 50);

    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        lru.clear();
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && lru.size() > sizeLimit ) {
                map.remove(lru.poll());
                lru.add(key);
            }
        }
    }
}
6
Xtra Coder

ワークロードに基づいて、ConcurrentSkipListMapは、範囲クエリが必要な場合、 KAFKA-8802 のような同期メソッドを使用したTreeMapよりも遅くなる可能性があります。

1
Anurag Sharma

ConcurrentHashMap:マルチスレッドインデックスベースのget/putが必要な場合、インデックスベースの操作のみがサポートされます。 Get/PutはO(1)です

ConcurrentSkipListMap:キーでソートされた上位/下位nアイテム、最後のエントリの取得、キーでソートされたマップ全体のフェッチ/トラバースなど、get/put以外の操作。複雑さはO(log(n))です。 ConcurrentHashMapと同じくらい素晴らしい。 SkipListを使用したConcurrentNavigableMapの実装ではありません。

要約すると、単純なgetおよびputだけでなく、ソートされた機能を必要とするマップに対してより多くの操作を行いたい場合にConcurrentSkipListMapを使用します。

0
spats