什么时候应该使用ConcurrentSkipListMap?


Answers:


79

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

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

另一方面,ConcurrentSkipListMap可确保各种操作的平均O(log(n))性能。它还不支持出于并发的考虑而进行调整。 ConcurrentSkipListMap还具有许多ConcurrentHashMap不执行的操作:ceilingEntry / Key,floorEntry / Key等。它还维护排序顺序,如果您使用,则必须计算(否则费用很高)ConcurrentHashMap

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

*尽管我希望实现与O(1)插入/查找的常规哈希映射保证大致相符;忽略重新哈希


好。Log(n)很好,但是ConcurrentSkipListMap是否节省空间?
DKSRathore

1
跳过列表必然大于Hashtables,但是ConcurrentHashMap的可调性使其可以构造比等效ConcurrentSkipListMap大的Hashtable。通常,我希望跳过列表更大,但是数量级相同。
凯文·蒙特罗斯

“它也不支持为了并发而进行调整”。为什么?链接是什么?
Pacerier

2
@Pacerier-我并不是说它不支持调整,因为它是并发的,我是说它不允许您调整影响其并发性能的参数(而ConcurrentHashMap可以)。
凯文·蒙特罗斯

@KevinMontrose Ic,所以您的意思是“它也不支持并发调整。”
Pacerier


7

就性能而言,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);
            }
        }
    }
}

1
我认为应该将ConcurrentSkipListMap与非并行计数器TreeMap进行比较。
阿巴斯(Abbas)

1
正如阿巴斯所言,将性能与ConcurrentHashMap进行比较似乎很愚蠢。ConcurrentSkipListMap的目的是(a)提供并发性,并且(b)维护键之间的排序顺序。ConcurrentHashMap执行a,但不执行b。将特斯拉和自卸车的0到60速度进行比较是毫无意义的,因为它们具有不同的用途。
罗勒·布尔克

虽然您不知道性能指标,但您不知道哪个是特斯拉,哪个是“自卸车” :)另外...您不知道没有指标的“ b”价格。因此,比较性能通常是有用的事情。
Xtra Coder

也许添加到树图的比较!:D
simgineer

3

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

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

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

ConcurrentHashMap

  • ed排序
  • ❌可导航
  • current并发

ConcurrentSkipListMap

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

下表引导您Map逐步了解与Java 11捆绑在一起的各种实现的主要功能。单击/轻击以放大。

Java 11中的地图实现表,比较它们的功能

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


1

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


感谢分享。我只是想在我的一个项目中用ConcurrentSkipListMap替换TreeMap,所以很高兴知道这一点!您是否有关于ConcurrentSkipListMap为何变慢的更多上下文,以及有关性能比较的更多详细信息?
yusong

0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.