我应该啥时候使用 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
慢,但为了抵消这一点,它支持SortedMap
、NavigableMap
和ConcurrentNavigableMap
接口。
【讨论】:
【参考方案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
也是如此。但如果你还想要SortedMap
和NavigableMap
的行为,请使用ConcurrentSkipListMap
ConcurrentHashMap
❌已排序
❌ 可导航
✅并发
ConcurrentSkipListMap
✅ 已排序
✅ 可导航
✅并发
下表指导您了解与 Java 11 捆绑的各种 Map
实现的主要功能。单击/点按可放大。
请记住,您可以从其他来源(例如 Google Guava)获取其他 Map
实现和类似的此类数据结构。
【讨论】:
这张照片太棒了。您是否有部分或全部正常和并发集合的类似图片? @Anv 谢谢,制作这张图表需要相当多的工作。这是我为 Java 用户组演示的一部分:Java Maps 的映射。而且,不,我只制作了另一个类图,forString
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)“数据属性”?
我应该啥时候使用 ConcurrentSkipListMap?