我什么时候应该使用ConcurrentSkipListMap?

时间:2009-11-28 06:33:26

标签: java performance multithreading map concurrenthashmap

在Java中,ConcurrentHashMap可以提供更好的multithreading解决方案。那我什么时候应该使用ConcurrentSkipListMap?这是一种冗余吗?

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

6 个答案:

答案 0 :(得分:64)

这两个类别在某些方面有所不同。

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

另一方面,

ConcurrentSkipListMap保证了各种操作的平均O(log(n))性能。它也不支持为并发调整。 ConcurrentSkipListMap还有许多ConcurrentHashMap没有的操作:ceilingEntry / Key,floorEntry / Key等。它还维护一个排序顺序,否则必须计算(以显着的费用)如果您使用ConcurrentHashMap

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

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

答案 1 :(得分:14)

有关数据结构的定义,请参阅Skip List。 ConcurrentSkipListMap以其键的自然顺序(或您定义的其他键顺序)存储Map。所以它的get / put / contains操作比HashMap慢,但为了抵消它,它支持SortedMap和NavigableMap接口。

答案 2 :(得分:6)

就性能而言,skipList何时用作地图 - 似乎慢了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);
            }
        }
    }
}

答案 3 :(得分:1)

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

答案 4 :(得分:1)

  

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

当您(a)需要对键进行排序,并且/或者(b)需要可导航地图的头/尾,头/尾和子图功能时。

ConcurrentHashMap类与ConcurrentMap一样,实现ConcurrentSkipListMap接口。但是,如果您还想要SortedMapNavigableMap的行为,请使用ConcurrentSkipListMap

ConcurrentHashMap

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

ConcurrentSkipListMap

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

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

A table of Map implementations bundled with Java with overview of major features.

请记住,您可以从Map等其他来源获得其他Google Guava实现以及类似的数据结构。

答案 5 :(得分:0)

ConcurrentHashMap:当您要基于多线程索引的获取/输出时,仅支持基于索引的操作。获取/输入为O(1)

ConcurrentSkipListMap:除了获取/放入以外,还有更多操作,例如按键对顶部/底部n个项目进行排序,获取最后一个条目,按键对整个地图进行获取/遍历等。复杂度为O(log(n)),因此性能不如ConcurrentHashMap好。它不是使用SkipList实现ConcurrentNavigableMap的。

总结,当您想在地图上执行更多需要排序功能而不是简单获取和放置的操作时,请使用ConcurrentSkipListMap。