En Java, ConcurrentHashMap
est là pour une meilleure solution multithreading
. Alors quand devrais-je utiliser ConcurrentSkipListMap
? Est-ce une redondance?
Est-ce que les aspects multithreading entre ces deux sont communs?
Ces deux classes varient de plusieurs manières.
ConcurrentHashMap ne garantit pas * l'exécution de ses opérations dans le cadre de son contrat. Il permet également d’ajuster certains facteurs de charge (en gros, le nombre de threads qui le modifient simultanément).
ConcurrentSkipListMap , en revanche, garantit des performances moyennes O(log(n)) sur une grande variété d'opérations. En outre, il ne prend pas en charge le réglage pour des raisons de concurrence. ConcurrentSkipListMap
a également un certain nombre d'opérations que ConcurrentHashMap
n'a pas: plafondEntry/Key, floorEntry/Key, etc. Il conserve également un ordre de tri, qui devrait sinon être calculé (à des coûts considérables) si vous utilisiez un ConcurrentHashMap
.
Fondamentalement, différentes implémentations sont fournies pour différents cas d'utilisation. Si vous avez besoin d'ajouter rapidement une paire clé/valeur unique et de rechercher une clé unique, utilisez la variable HashMap
. Si vous avez besoin d'une traversée dans l'ordre plus rapide et que vous pouvez vous permettre un coût supplémentaire pour l'insertion, utilisez la variable SkipListMap
.
* Bien que je s’attende à ce que l’implémentation soit à peu près conforme aux garanties générales d’insertion/recherche de O(1) par carte de hachage; ignorer le re-hachage
Voir Skip List pour une définition de la structure de données. ConcurrentSkipListMap stocke la carte dans l'ordre naturel de ses clés (ou dans un autre ordre de clés que vous définissez). Donc, il aura des opérations get/put/contient plus lentes qu'un HashMap, mais pour compenser cela, il supporte les interfaces SortedMap et NavigableMap.
En termes de performances, skipList
quand est utilisé en tant que carte - semble être 10 à 20 fois plus lent. Voici le résultat de mes tests (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
Et en plus de cela - un cas d'utilisation où comparer un à un est vraiment logique. Implémentation du cache des éléments récemment utilisés en utilisant ces deux collections. Maintenant, l'efficacité de skipList semble être plus douteuse.
MyBenchmark.hashMap_put1000_lru avgt 5 0.032 ? 0.001 s/op
MyBenchmark.skipListMap_put1000_lru avgt 5 3.332 ? 0.124 s/op
Voici le code pour JMH (exécuté en tant que 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);
}
}
}
}
Sur la base des charges de travail, ConcurrentSkipListMap pourrait être plus lent que TreeMap avec des méthodes synchronisées comme dans KAFKA-8802 si des requêtes de plage sont nécessaires.
ConcurrentHashMap: lorsque vous voulez obtenir/mettre en place un index multithread, seules les opérations basées sur un index sont prises en charge. Get/Put sont de O (1)
ConcurrentSkipListMap: Plus d'opérations que juste get/put, comme les n éléments haut/bas triés par clé, obtenir la dernière entrée, chercher/traverser la carte entière triée par clé, etc. La complexité est de O (log (n)) aussi bien que ConcurrentHashMap. Ce n'est pas une implémentation de ConcurrentNavigableMap avec SkipList.
Pour résumer, utilisez ConcurrentSkipListMap lorsque vous souhaitez effectuer davantage d’opérations sur la carte nécessitant des entités triées plutôt que de simples opérations d’obtention et de mise.