En Java, ConcurrentHashMap
existe una mejor multithreading
solución. Entonces, ¿cuándo debería usar ConcurrentSkipListMap
? ¿Es una redundancia?
¿Son comunes los aspectos de subprocesos múltiples entre estos dos?
En Java, ConcurrentHashMap
existe una mejor multithreading
solución. Entonces, ¿cuándo debería usar ConcurrentSkipListMap
? ¿Es una redundancia?
¿Son comunes los aspectos de subprocesos múltiples entre estos dos?
Respuestas:
Estas dos clases varían de varias formas.
ConcurrentHashMap no garantiza * el tiempo de ejecución de sus operaciones como parte de su contrato. También permite ajustar ciertos factores de carga (aproximadamente, el número de subprocesos que lo modifican simultáneamente).
ConcurrentSkipListMap , por otro lado, garantiza un rendimiento promedio de O (log (n)) en una amplia variedad de operaciones. Tampoco admite el ajuste por simultaneidad. ConcurrentSkipListMap
también tiene una serie de operaciones que ConcurrentHashMap
no las tiene: techoEntry / Key, floorEntry / Key, etc. También mantiene un orden de clasificación, que de otro modo tendría que calcularse (a un costo considerable) si estuviera usando un ConcurrentHashMap
.
Básicamente, se proporcionan diferentes implementaciones para diferentes casos de uso. Si necesita una adición rápida de un par de clave / valor y una búsqueda rápida de una sola clave, use el HashMap
. Si necesita un recorrido en orden más rápido y puede pagar el costo adicional de la inserción, use la extensión SkipListMap
.
* Aunque espero que la implementación esté aproximadamente en línea con las garantías generales del mapa hash de la inserción / búsqueda de O (1); ignorando el re-hash
Consulte Lista de omisión para obtener una definición de la estructura de datos.
A ConcurrentSkipListMap
almacena el Map
en el orden natural de sus claves (o algún otro orden de claves que usted defina). Por lo que tendrá más lentos get
/ put
/ contains
operaciones que una HashMap
, pero para compensar este es compatible con los SortedMap
, NavigableMap
y ConcurrentNavigableMap
las interfaces.
En términos de rendimiento, skipList
cuando se usa como mapa, parece ser de 10 a 20 veces más lento. Aquí está el resultado de mis pruebas (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
Y además de eso, caso de uso en el que comparar uno con otro realmente tiene sentido. Implementación de la caché de los últimos elementos utilizados recientemente utilizando ambas colecciones. Ahora la eficiencia de skipList parece ser aún más dudosa.
MyBenchmark.hashMap_put1000_lru avgt 5 0.032 ? 0.001 s/op
MyBenchmark.skipListMap_put1000_lru avgt 5 3.332 ? 0.124 s/op
Aquí está el código para JMH (ejecutado como 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);
}
}
}
}
Entonces, ¿cuándo debería usar ConcurrentSkipListMap?
Cuando (a) necesita mantener las claves ordenadas, y / o (b) necesita las características de primer / último, cabeza / cola y submapa de un mapa navegable.
La ConcurrentHashMap
clase implementa la ConcurrentMap
interfaz, al igual que lo hace ConcurrentSkipListMap
. Pero si también quieres los comportamientos de SortedMap
y NavigableMap
, usaConcurrentSkipListMap
ConcurrentHashMap
ConcurrentSkipListMap
Aquí hay una tabla que lo guía a través de las principales características de las diversas Map
implementaciones incluidas con Java 11. Haga clic / toque para ampliar.
Tenga en cuenta que puede obtener otras Map
implementaciones y estructuras de datos similares de otras fuentes, como Google Guava .
String
clases para las clases y la interfaz relacionadas .
Según las cargas de trabajo, ConcurrentSkipListMap podría ser más lento que TreeMap con métodos sincronizados como en KAFKA-8802 si se necesitan consultas de rango.
ConcurrentHashMap: cuando desee obtener / poner basado en índices multiproceso, solo se admiten operaciones basadas en índices. Get / Put son de O (1)
ConcurrentSkipListMap: más operaciones que solo obtener / poner, como n elementos de arriba / abajo ordenados por clave, obtener la última entrada, buscar / recorrer el mapa completo ordenado por clave, etc. La complejidad es de O (log (n)), así que el rendimiento de la puesta no tan bueno como ConcurrentHashMap. No es una implementación de ConcurrentNavigableMap con SkipList.
Para resumir, use ConcurrentSkipListMap cuando desee realizar más operaciones en el mapa que requieran características ordenadas en lugar de simplemente obtener y colocar.