我应该啥时候使用 ConcurrentSkipListMap?

Posted

技术标签:

【中文标题】我应该啥时候使用 ConcurrentSkipListMap?【英文标题】:When should I use ConcurrentSkipListMap?我应该什么时候使用 ConcurrentSkipListMap? 【发布时间】:2010-12-21 04:08:16 【问题描述】:

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

这两者之间的多线程方面是否共同?

【问题讨论】:

【参考方案1】:

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

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

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

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

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

【讨论】:

好的。 Log(n) 很好,但 ConcurrentSkipListMap 是否节省空间? 跳过列表必然比 Hashtable 大,但 ConcurrentHashMap 的可调特性使得构造比等效 ConcurrentSkipListMap 更大的 Hashtable 成为可能。一般来说,我希望跳过列表更大,但数量级相同。 “它也不支持为并发进行调整”.. 为什么?链接是什么? @Pacerier - 我并不是说它支持调优,因为它是并发的,我的意思是它不允许你调整影响它并发性能的参数(而 ConcurrentHashMap 支持)。 @KevinMontrose Ic,所以你的意思是“它也不支持并发调整。”【参考方案2】:

已排序、可导航和并发

有关数据结构的定义,请参阅Skip List。

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

【讨论】:

【参考方案3】:

在性能方面,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);
            
        
    

【讨论】:

我认为 ConcurrentSkipListMap 应该与它们的非并发对应部分 TreeMap 进行比较。 正如 abbas 评论的那样,将性能与 ConcurrentHashMap 进行比较似乎很愚蠢。 ConcurrentSkipListMap 的目的是 (a) 提供并发性,以及 (b) 维护键之间的排序顺序。 ConcurrentHashMap 执行 a,但不执行 b。比较特斯拉和自卸卡车从 0 到 60 的速度是没有意义的,因为它们的用途不同。 虽然您不知道性能指标,但您不知道哪个是特斯拉,哪个是“自卸车”:) 另外……您不知道“b”的价格指标。因此 - 比较性能通常是有用的。 也许可以添加一个与树形图的比较! :D 跳过列表的内存效率更高。对我来说,这比查找几毫秒更重要。【参考方案4】:

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

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

ConcurrentHashMap 类实现了ConcurrentMap 接口,ConcurrentSkipListMap 也是如此。但如果你还想要SortedMapNavigableMap 的行为,请使用ConcurrentSkipListMap

ConcurrentHashMap

❌已排序 ❌ 可导航 ✅并发

ConcurrentSkipListMap

✅ 已排序 ✅ 可导航 ✅并发

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

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

【讨论】:

这张照片太棒了。您是否有部分或全部正常和并发集合的类似图片? @Anv 谢谢,制作这张图表需要相当多的工作。这是我为 Java 用户组演示的一部分:Java Maps 的映射。而且,不,我只制作了另一个类图,for String related classes and interface。【参考方案5】:

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

【讨论】:

感谢分享。我只是想在我的一个项目中用 ConcurrentSkipListMap 替换 TreeMap,所以很高兴知道这一点!您是否有更多关于 ConcurrentSkipListMap 为何较慢的背景信息以及有关性能比较的更多详细信息?【参考方案6】:

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

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

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

【讨论】:

以上是关于我应该啥时候使用 ConcurrentSkipListMap?的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候应该使用“隐藏文本框”,啥时候应该使用(html 5)“数据属性”?

我啥时候应该在 C 中使用 malloc,啥时候不应该?

我啥时候应该在 C 中使用 malloc,啥时候不应该?

我应该啥时候使用 ConcurrentSkipListMap?

servletcontext.getRealPath("/") 是啥意思,我应该啥时候使用它

我啥时候应该使用 FutureBuilder?