java collections concurrency java.util.concurrent

java - Implementación de BlockingQueue: ¿Cuáles son las diferencias entre SynchronousQueue y LinkedBlockingQueue?



collections concurrency (3)

Actualmente, los Executors predeterminados (basados ​​en ThreadPoolExecutor ) pueden usar un conjunto de subprocesos creados previamente de un tamaño fijo y un BlockingQueue de algún tamaño para cualquier desbordamiento o crear subprocesos de hasta un tamaño máximo si (y solo si) esa cola está llena .

Esto lleva a algunas propiedades sorprendentes. Por ejemplo, como los subprocesos adicionales solo se crean una vez que se alcanza la capacidad de la cola, el uso de un LinkedBlockingQueue (que no tiene límites) significa que los nuevos subprocesos nunca se crearán, incluso si el tamaño del grupo actual es cero. Si usa un ArrayBlockingQueue , los nuevos hilos se crean solo si están llenos, y existe una probabilidad razonable de que los trabajos posteriores sean rechazados si el grupo no ha borrado espacio para entonces.

Un SynchronousQueue tiene capacidad cero, por lo que un productor bloquea hasta que un consumidor esté disponible o se cree un subproceso. Esto significa que, a pesar de las impresionantes figuras de aspecto producidas por @axtavt, un grupo de subprocesos en caché generalmente tiene el peor rendimiento desde el punto de vista del productor.

Lamentablemente, actualmente no existe una buena versión de la biblioteca de una implementación de compromiso que creará subprocesos durante ráfagas o actividad hasta un máximo desde un mínimo mínimo. Usted tiene una piscina cultivable o una fija. Tenemos uno internamente, pero todavía no está listo para el consumo público.

Veo esta implementación de BlockingQueue y no puedo entender las diferencias entre ellos. Mi conclusión hasta ahora:

  1. Nunca necesitaré SynchronousQueue
  2. LinkedBlockingQueue garantiza FIFO, BlockingQueue debe crearse con el parámetro true para que sea FIFO
  3. SynchronousQueue rompe el método de la mayoría de las colecciones (contiene, tamaño, etc.)

Entonces, ¿cuándo necesito SynchronousQueue ? ¿El rendimiento de esta implementación es mejor que LinkedBlockingQueue ?

Para hacerlo más complicado ... ¿por qué Executors.newCachedThreadPool usa SynchronousQueue cuando los otros ( Executors.newSingleThreadExecutor y Executors.newFixedThreadPool ) usan LinkedBlockingQueue?

EDITAR

La primera pregunta está resuelta. Pero todavía no entiendo por qué Executors.newCachedThreadPool usa SynchronousQueue cuando los demás ( Executors.newSingleThreadExecutor y Executors.newFixedThreadPool ) usan LinkedBlockingQueue?

Lo que obtengo es que con SynchronousQueue, el productor será bloqueado si no hay un hilo libre. Pero como el número de subprocesos es prácticamente ilimitado (se crearán nuevos subprocesos si es necesario), esto nunca sucederá. Entonces, ¿por qué debería usar SynchronousQueue?


El grupo de subprocesos de caché crea subprocesos a pedido. Necesita una cola que pase la tarea a un consumidor que espera o falla. Si no hay un consumidor en espera, crea un nuevo hilo. SynchronousQueue no contiene un elemento, en su lugar, pasa el elemento o falla.


SynchronousQueue es un tipo de cola muy especial: implementa un enfoque de encuentro (el productor espera hasta que el consumidor esté listo, el consumidor espera hasta que el productor esté listo) detrás de la interfaz de Queue .

Por lo tanto, puede necesitarlo solo en los casos especiales en los que necesite esa semántica particular, por ejemplo, enhebrar una tarea individualmente sin poner en cola otras solicitudes .

Otra razón para usar SynchronousQueue es el rendimiento. La implementación de SynchronousQueue parece estar fuertemente optimizada, por lo que si no necesita nada más que un punto de encuentro (como en el caso de Executors.newCachedThreadPool() , donde los consumidores se crean "a pedido", por lo que los elementos de la cola no t acumular), puede obtener una ganancia de rendimiento utilizando SynchronousQueue .

La prueba sintética simple muestra que, en un solo productor simple, un escenario de consumidor único en el rendimiento de la máquina de doble núcleo de SynchronousQueue es ~ 20 veces mayor que el rendimiento de LinkedBlockingQueue y ArrayBlockingQueue con longitud de cola = 1. Cuando la longitud de la cola aumenta, su rendimiento aumenta y casi Alcanza el rendimiento de SynchronousQueue . Esto significa que SynchronousQueue tiene una sobrecarga de sincronización baja en máquinas de múltiples núcleos en comparación con otras colas. Pero, nuevamente, solo importa en circunstancias específicas cuando necesita un punto de encuentro disfrazado de Queue .

EDITAR:

Aquí hay una prueba:

public class Test { static ExecutorService e = Executors.newFixedThreadPool(2); static int N = 1000000; public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { int length = (i == 0) ? 1 : i * 5; System.out.print(length + "/t"); System.out.print(doTest(new LinkedBlockingQueue<Integer>(length), N) + "/t"); System.out.print(doTest(new ArrayBlockingQueue<Integer>(length), N) + "/t"); System.out.print(doTest(new SynchronousQueue<Integer>(), N)); System.out.println(); } e.shutdown(); } private static long doTest(final BlockingQueue<Integer> q, final int n) throws Exception { long t = System.nanoTime(); e.submit(new Runnable() { public void run() { for (int i = 0; i < n; i++) try { q.put(i); } catch (InterruptedException ex) {} } }); Long r = e.submit(new Callable<Long>() { public Long call() { long sum = 0; for (int i = 0; i < n; i++) try { sum += q.take(); } catch (InterruptedException ex) {} return sum; } }).get(); t = System.nanoTime() - t; return (long)(1000000000.0 * N / t); // Throughput, items/sec } }

Y aquí hay un resultado en mi máquina: