He encontrado que podemos obtener un conjunto de hash concurrente usando newKeySet();
o con la keySet(default value)
de una ConcurrentHashMap
. ¿Es esta la mejor manera de crear un conjunto de hilos de proceso seguro cuando las operaciones de escritura son el exceso de la operación de lectura.
He leído acerca CopyOnWriteArraySet
parece que es mejor cuando la lectura es el exceso de escritura.
Da la bienvenida a todas las respuestas que nos pueden ayudar a saber un poco más sobre esto.
ConcurrentHashMap.newKeySet()
y ConcurrentHashMap.keySet()
que el retorno de una KeySetView
dependen de la ConcurrentHashMap
clase que se bloquea en la escritura sólo y únicamente la asignación de teclas preocupados por la escritura y no todo el mapa.
En consecuencia, las operaciones de lectura son rápidos pero escribir también (muy ligeramente más lento en los hechos).
CopyOnWriteArraySet
que se basa en CopyOnWriteArrayList
no bloquea para la lectura pero cerraduras para las operaciones de escritura. Como consecuencia de ello para garantizar la consistencia de la lectura simultánea de cada operación de escritura desencadena una copia de la matriz subyacente.
Así, en el caso de no pequeña colección ( 100
elemento o más, por ejemplo), CopyOnWriteArraySet
operaciones de escritura deben ser más caro (bloqueo en toda la colección + copia de la matriz subyacente) que KeySetView
(bloqueo sólo y únicamente la entrada en cuestión).
El CopyOnWriteArraySet
javadoc subraya ese punto:
Es el más adecuado para aplicaciones en las que fije los tamaños generalmente se mantienen pequeñas, las operaciones de sólo lectura superan enormemente las operaciones mutativas , y que necesita para evitar interferencias entre los hilos durante el recorrido.
operaciones mutativas (ADD, juego, eliminar, etc.) son caros, ya que por lo general implican la copia de toda la matriz subyacente
Este es un punto de referencia donde se compara el comportamiento tanto.
Los Set<String>
s se inicializan con 100 elementos ( "0" a 99" de valor) en cada iteración.
Los 6 primeros métodos son operaciones de escritura (eliminar, añadir un nuevo elemento, sobrescribir un elemento existente).
Mientras que los 4 próximos métodos son operaciones de lectura ( iterate, contiene).
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(8)
public class SetBenchmark {
private Set<String> keySetView;
private Set<String> copyOnWriteArraySet;
private Random random;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(SetBenchmark.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
@Setup(Level.Iteration)
public void doSetup() {
random = new Random(1);
keySetView = ConcurrentHashMap.newKeySet();
copyOnWriteArraySet = new CopyOnWriteArraySet<>();
init(keySetView);
init(copyOnWriteArraySet);
}
private void init(Set<String> set) {
IntStream.range(0, 100)
.forEach(i -> {
final String string = String.valueOf((char) i);
set.add(string);
});
}
// Writing
@Benchmark
public void _1_keySetView_remove() {
doRemove(keySetView);
}
@Benchmark
public void _2_copyOnWriteArraySet_remove() {
doRemove(copyOnWriteArraySet);
}
@Benchmark
public void _3_keySetView_add_with_new_value() {
doAddWithNewValue(keySetView);
}
@Benchmark
public void _4_copyOnWriteArraySet_add_with_new_value() {
doAddWithNewValue(copyOnWriteArraySet);
}
@Benchmark
public void _5_keySetView_add_with_existing_value() {
doAddWithExistingValue(keySetView);
}
@Benchmark
public void _6_copyOnWriteArraySet_add_with_existing_value() {
doAddWithExistingValue(copyOnWriteArraySet);
}
// Reading
@Benchmark
public void _7_keySetView_iterate() {
String res = doIterate(keySetView);
}
@Benchmark
public void _8_copyOnWriteArraySet_iterate() {
String res = doIterate(copyOnWriteArraySet);
}
@Benchmark
public void _9_keySetView_contains() {
boolean res = doContains(keySetView);
}
@Benchmark
public void _010_copyOnWriteArraySet_contains() {
boolean res = doContains(copyOnWriteArraySet);
}
// Writing
private void doRemove(Set<String> set) {
set.remove(getRandomString());
}
private void doAddWithNewValue(Set<String> set) {
set.add(getRandomString() + set.size());
}
private void doAddWithExistingValue(Set<String> set) {
set.add(getRandomString());
}
// Reading
private String doIterate(Set<String> set) {
String result = "";
for (String string : set) {
result += string;
}
return result;
}
private boolean doContains(Set<String> set) {
return set.contains(getRandomString());
}
// Random value with seed
private String getRandomString() {
return String.valueOf(random.nextInt(100));
}
}
No es el resultado (puntuación más baja es mejor).
las operaciones de escritura:
Modo de referencia Cnt Puntuación de error Unidades SetBenchmark._1_keySetView_remove avgt 61.659 ns / op SetBenchmark._2_copyOnWriteArraySet_remove avgt 249,976 ns / op SetBenchmark._3_keySetView_add_with_new_value avgt 240,589 ns / op SetBenchmark._4_copyOnWriteArraySet_add_with_new_value avgt 30691,318 ns / op SetBenchmark._5_keySetView_add_with_existing_value avgt 84,472 ns / OP SetBenchmark. _6_copyOnWriteArraySet_add_with_existing_value avgt 473,592 ns / op
las operaciones de lectura:
Moda referencia Cnt Puntuación de error Unidades SetBenchmark._7_keySetView_iterate avgt 13603.012 ns / en SetBenchmark._8_copyOnWriteArraySet_iterate avgt 13626.146 ns / en SetBenchmark._9_keySetView_contains avgt 53.081 ns / en SetBenchmark._10_copyOnWriteArraySet_contains avgt 250.401 ns / en
Lectura son tan rápidos para las dos Set
implementaciones sólo para el iterador.
Y la escritura es mucho más lento para CopyOnWriteArraySet
en todo caso, pero cuando add()
un valor no existente, que es todavía peor.