Pool di thread personalizzati in Java 8 flussi paralleli

1. Panoramica

Java 8 ha introdotto il concetto di stream come un modo efficiente per eseguire operazioni di massa sui dati. E è possibile ottenere flussi paralleli in ambienti che supportano la concorrenza.

Questi flussi possono offrire prestazioni migliorate, al costo del sovraccarico del multi-threading.

In questo breve tutorial, esamineremo uno dei maggiori limiti dell'API Stream e vedremo come far funzionare un flusso parallelo con un'istanza ThreadPool personalizzata , in alternativa: c'è una libreria che lo gestisce.

2. Flusso parallelo

Cominciamo con un semplice esempio, chiamando il metodo parallelStream su uno qualsiasi dei tipi Collection , che restituirà un flusso possibilmente parallelo :

@Test public void givenList_whenCallingParallelStream_shouldBeParallelStream(){ List aList = new ArrayList(); Stream parallelStream = aList.parallelStream(); assertTrue(parallelStream.isParallel()); }

L'elaborazione predefinita che si verifica in tale flusso utilizza ForkJoinPool.commonPool (), un pool di thread condiviso dall'intera applicazione.

3. Pool di thread personalizzato

Possiamo effettivamente passare un ThreadPool personalizzato durante l'elaborazione del flusso .

L'esempio seguente consente a un flusso parallelo di utilizzare un pool di thread personalizzato per calcolare la somma dei valori lunghi da 1 a 1.000.000, inclusi:

@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); }

Abbiamo utilizzato il costruttore ForkJoinPool con un livello di parallelismo 4. Sono necessari alcuni esperimenti per determinare il valore ottimale per diversi ambienti, ma una buona regola pratica è semplicemente scegliere il numero in base al numero di core della CPU.

Successivamente, abbiamo elaborato il contenuto del flusso parallelo , riassumendolo nella chiamata di riduzione .

Questo semplice esempio potrebbe non dimostrare la piena utilità dell'utilizzo di un pool di thread personalizzato , ma i vantaggi diventano evidenti in situazioni in cui non si desidera collegare il pool di thread comune con attività a esecuzione prolungata (ad esempio l'elaborazione dei dati da un'origine di rete) o il pool di thread comune viene utilizzato da altri componenti all'interno dell'applicazione.

4. Conclusione

Abbiamo esaminato brevemente come eseguire un flusso parallelo utilizzando un pool di thread personalizzato . Nell'ambiente giusto e con l'uso corretto del livello di parallelismo, è possibile ottenere miglioramenti delle prestazioni in determinate situazioni.

Gli esempi di codice completi a cui si fa riferimento in questo articolo sono disponibili su GitHub.