1. Información general
Java 8 introdujo el concepto de S treams como una forma eficiente de realizar operaciones masivas con datos. Y las transmisiones paralelas se pueden obtener en entornos que admiten la simultaneidad.
Estos flujos pueden tener un rendimiento mejorado, a costa de una sobrecarga de subprocesos múltiples.
En este tutorial rápido, veremos una de las mayores limitaciones de Stream API y veremos cómo hacer que un flujo paralelo funcione con una instancia personalizada de ThreadPool , alternativamente, hay una biblioteca que maneja esto.
2. Secuencia paralela
Comencemos con un ejemplo simple, llamando al método paraleloStream en cualquiera de los tipos de Colección , que devolverá un flujo posiblemente paralelo :
@Test public void givenList_whenCallingParallelStream_shouldBeParallelStream(){ List aList = new ArrayList(); Stream parallelStream = aList.parallelStream(); assertTrue(parallelStream.isParallel()); }
El procesamiento predeterminado que se produce en una secuencia de este tipo utiliza ForkJoinPool.commonPool (), un grupo de subprocesos compartido por toda la aplicación.
3. Grupo de subprocesos personalizados
De hecho, podemos pasar un ThreadPool personalizado al procesar la transmisión .
El siguiente ejemplo permite que un Stream paralelo use un Thread Pool personalizado para calcular la suma de valores largos de 1 a 1,000,000, inclusive:
@Test public void giveRangeOfLongs_whenSummedInParallel_shouldBeEqualToExpectedTotal() throws InterruptedException, ExecutionException { long firstNum = 1; long lastNum = 1_000_000; List aList = LongStream.rangeClosed(firstNum, lastNum).boxed() .collect(Collectors.toList()); ForkJoinPool customThreadPool = new ForkJoinPool(4); long actualTotal = customThreadPool.submit( () -> aList.parallelStream().reduce(0L, Long::sum)).get(); assertEquals((lastNum + firstNum) * lastNum / 2, actualTotal); }
Usamos el constructor ForkJoinPool con un nivel de paralelismo de 4. Se requiere algo de experimentación para determinar el valor óptimo para diferentes entornos, pero una buena regla general es simplemente elegir el número en función de cuántos núcleos tiene su CPU.
A continuación, procesamos el contenido del Stream paralelo , resumiéndolos en la llamada reducida .
Es posible que este simple ejemplo no demuestre la utilidad total de usar un Thread Pool personalizado , pero los beneficios se vuelven obvios en situaciones en las que no queremos vincular el Thread Pool común con tareas de larga ejecución (por ejemplo, procesar datos de una fuente de red) , o el grupo de subprocesos común está siendo utilizado por otros componentes dentro de la aplicación.
4. Conclusión
Hemos analizado brevemente cómo ejecutar un flujo paralelo utilizando un grupo de subprocesos personalizado . En el entorno adecuado y con el uso adecuado del nivel de paralelismo, se pueden obtener ganancias de rendimiento en determinadas situaciones.
Los ejemplos de código completos a los que se hace referencia en este artículo se pueden encontrar en Github.