90

在Java中,ConcurrentHashMap有没有更好的multithreading解决方案。那我应该什么时候使用ConcurrentSkipListMap?是冗余吗?

这两者之间的多线程方面是否常见?

4

6 回答 6

83

这两个类在几个方面有所不同。

ConcurrentHashMap不保证*将其操作的运行时间作为其合约的一部分。它还允许调整某些负载因子(大致是同时修改它的线程数)。

另一方面, ConcurrentSkipListMap保证了各种操作的平均 O(log(n)) 性能。它也不支持为了并发性而进行的调优。 ConcurrentSkipListMap还有一些ConcurrentHashMap没有的操作:ceilingEntry/Key、floorEntry/Key 等。它还维护一个排序顺序,如果您使用ConcurrentHashMap.

基本上,为不同的用例提供了不同的实现。如果您需要快速添加单键/值对和快速单键查找,请使用HashMap. 如果您需要更快的中序遍历,并且可以负担插入的额外成本,请使用SkipListMap.

*尽管我希望实现大致符合 O(1) 插入/查找的一般哈希映射保证;忽略重新散列

于 2009-11-28T06:50:03.743 回答
17

已排序、可导航和并发

有关数据结构的定义,请参见跳过列表

A按其键的自然顺序(或您定义的其他键顺序)ConcurrentSkipListMap存储。Map所以它的 // 操作会比 a 慢get,但put为了抵消这一点,它支持,和接口。containsHashMapSortedMapNavigableMapConcurrentNavigableMap

于 2009-11-28T06:48:45.277 回答
9

在性能方面,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);
            }
        }
    }
}
于 2016-09-15T08:53:04.610 回答
8

那我什么时候应该使用 ConcurrentSkipListMap?

当您 (a) 需要保持键排序,和/或 (b) 需要可导航地图的第一个/最后一个、头/尾和子图特征时。

该类ConcurrentHashMap实现了ConcurrentMap接口,就像ConcurrentSkipListMap. 但是,如果您还想要 and 的行为SortedMap,请NavigableMap使用ConcurrentSkipListMap

ConcurrentHashMap

  • ❌ 排序
  • ❌ 可导航
  • ✅并发

ConcurrentSkipListMap

  • ✅ 分类
  • ✅ 可导航
  • ✅并发

下表指导您了解Map与 Java 11 捆绑的各种实现的主要功能。单击/点击以放大。

Java 11 中的地图实现表,比较它们的特性

请记住,您可以Map从其他来源(例如Google Guava )获得其他实现和类似的此类数据结构。

于 2019-09-07T07:37:44.190 回答
1

如果需要范围查询,基于工作负载,ConcurrentSkipListMap 可能比使用KAFKA-8802中的同步方法的 TreeMap 慢。

于 2019-08-14T18:44:16.383 回答
0

ConcurrentHashMap :当您想要基于多线程索引的 get/put 时,仅支持基于索引的操作。获取/放置是 O(1)

ConcurrentSkipListMap : 更多的操作不仅仅是获取/放置,比如按键排序的顶部/底部 n 个项目,获取最后一个条目,获取/遍历按键排序的整个地图等。复杂性是 O(log(n)),所以放置性能不是和 ConcurrentHashMap 一样好。它不是带有 SkipList 的 ConcurrentNavigableMap 的实现。

总结一下,当您想在需要排序特征的地图上执行更多操作时,使用 ConcurrentSkipListMap 而不仅仅是简单的 get 和 put。

于 2019-02-06T11:26:26.217 回答