在Java中,ConcurrentHashMap
有更好的multithreading
解决方案。那我ConcurrentSkipListMap
什么时候应该使用?这是多余的吗?
两者之间的多线程方面是否常见?
在Java中,ConcurrentHashMap
有更好的multithreading
解决方案。那我ConcurrentSkipListMap
什么时候应该使用?这是多余的吗?
两者之间的多线程方面是否常见?
Answers:
这两个类在某些方面有所不同。
ConcurrentHashMap不保证*其合同约定的运行时间。它还允许调整某些负载因子(大约是同时修改它的线程数)。
另一方面,ConcurrentSkipListMap可确保各种操作的平均O(log(n))性能。它还不支持出于并发的考虑而进行调整。 ConcurrentSkipListMap
还具有许多ConcurrentHashMap
不执行的操作:ceilingEntry / Key,floorEntry / Key等。它还维护排序顺序,如果您使用,则必须计算(否则费用很高)ConcurrentHashMap
。
基本上,为不同的用例提供了不同的实现。如果您需要快速添加单键/值对和快速查找单键,请使用HashMap
。如果您需要更快的顺序遍历,并且可以负担额外的插入费用,请使用SkipListMap
。
*尽管我希望实现与O(1)插入/查找的常规哈希映射保证大致相符;忽略重新哈希
有关数据结构的定义,请参见跳过列表。
一个ConcurrentSkipListMap
存储Map
在它的键的自然顺序(或者你定义了其他一些重要的顺序)。因此,这将有慢get
/ put
/contains
不是一个业务HashMap
,而是来抵消这种支持的SortedMap
,NavigableMap
和ConcurrentNavigableMap
接口。
就性能而言,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?
当您(a)需要对键进行排序,和/或(b)需要可导航地图的头/尾,头/尾和子图功能时。
本ConcurrentHashMap
类实现了ConcurrentMap
接口,一样ConcurrentSkipListMap
。但是,如果你也想的行为SortedMap
和NavigableMap
,使用ConcurrentSkipListMap
ConcurrentHashMap
ConcurrentSkipListMap
下表引导您Map
逐步了解与Java 11捆绑在一起的各种实现的主要功能。单击/轻击以放大。
请记住,您可以Map
从其他来源(例如Google Guava)获得其他实现以及类似的数据结构。
如果需要范围查询,基于工作量,ConcurrentSkipListMap可能比使用KAFKA-8802中的同步方法的TreeMap慢。
ConcurrentHashMap:当您想要基于多线程索引的获取/输出时,仅支持基于索引的操作。获取/放入O(1)
ConcurrentSkipListMap:除了获取/放置以外,还需要更多操作,例如按键对顶部/底部n个项目进行排序,获取最后一个条目,按键对整个地图进行获取/遍历等。复杂度为O(log(n)),因此放置性能不是和ConcurrentHashMap一样好。它不是使用SkipList实现的ConcurrentNavigableMap。
总结一下,当您想在地图上执行更多需要排序功能的操作,而不仅仅是简单的获取和放置操作时,请使用ConcurrentSkipListMap。