Il pool di thread Java gestisce il pool di thread lavoratori. Contiene una coda che mantiene i task in attesa di essere eseguiti. Possiamo utilizzare ThreadPoolExecutor
per creare un pool di thread in Java. Il pool di thread Java gestisce la collezione di thread Runnable. I thread lavoratori eseguono i thread Runnable dalla coda. java.util.concurrent.Executors fornisce metodi di fabbrica e di supporto per l’interfaccia java.util.concurrent.Executor per creare il pool di thread in Java. Executors è una classe di utilità che fornisce anche metodi utili per lavorare con le classi ExecutorService, ScheduledExecutorService, ThreadFactory e Callable attraverso vari metodi di fabbrica. Scriviamo un semplice programma per spiegare il suo funzionamento. Prima di tutto, abbiamo bisogno di una classe Runnable, chiamata WorkerThread.java
package com.journaldev.threadpool;
public class WorkerThread implements Runnable {
private String command;
public WorkerThread(String s){
this.command=s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Start. Command = "+command);
processCommand();
System.out.println(Thread.currentThread().getName()+" End.");
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString(){
return this.command;
}
}
Esempio di ExecutorService
Ecco la classe di programma di test SimpleThreadPool.java
, in cui stiamo creando un pool di thread fissato utilizzando il framework Executors.
package com.journaldev.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
Nel programma sopra, stiamo creando un pool di thread di dimensione fissa con 5 thread lavoratori. Poi stiamo sottoponendo 10 job a questo pool, poiché la dimensione del pool è 5, inizierà a lavorare su 5 job e gli altri job saranno in stato di attesa. Non appena uno dei job viene completato, un altro job dalla coda di attesa verrà preso dal thread lavoratore ed eseguito. Ecco l’output del programma sopra.
pool-1-thread-2 Start. Command = 1
pool-1-thread-4 Start. Command = 3
pool-1-thread-1 Start. Command = 0
pool-1-thread-3 Start. Command = 2
pool-1-thread-5 Start. Command = 4
pool-1-thread-4 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
pool-1-thread-3 End.
pool-1-thread-3 Start. Command = 8
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 9
pool-1-thread-1 Start. Command = 7
pool-1-thread-5 Start. Command = 6
pool-1-thread-4 Start. Command = 5
pool-1-thread-2 End.
pool-1-thread-4 End.
pool-1-thread-3 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
Finished all threads
L’output conferma che ci sono cinque thread nel pool denominati da “pool-1-thread-1” a “pool-1-thread-5” e sono responsabili dell’esecuzione dei compiti inviati al pool.
Esempio di ThreadPoolExecutor
La classe Executors fornisce un’implementazione semplice di ExecutorService utilizzando ThreadPoolExecutor, ma ThreadPoolExecutor offre molte più funzionalità. Possiamo specificare il numero di thread che saranno attivi quando creiamo un’istanza di ThreadPoolExecutor e possiamo limitare la dimensione del pool di thread e creare la nostra implementazione RejectedExecutionHandler per gestire i lavori che non possono essere inseriti nella coda dei worker. Ecco la nostra implementazione personalizzata dell’interfaccia RejectedExecutionHandler.
package com.journaldev.threadpool;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + " is rejected");
}
}
ThreadPoolExecutor
fornisce diversi metodi tramite i quali possiamo scoprire lo stato attuale dell’esecutore, la dimensione del pool, il conteggio dei thread attivi e il conteggio dei compiti. Quindi ho un thread di monitoraggio che stamperà le informazioni sull’esecutore a determinati intervalli di tempo.
package com.journaldev.threadpool;
import java.util.concurrent.ThreadPoolExecutor;
public class MyMonitorThread implements Runnable
{
private ThreadPoolExecutor executor;
private int seconds;
private boolean run=true;
public MyMonitorThread(ThreadPoolExecutor executor, int delay)
{
this.executor = executor;
this.seconds=delay;
}
public void shutdown(){
this.run=false;
}
@Override
public void run()
{
while(run){
System.out.println(
String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
this.executor.getPoolSize(),
this.executor.getCorePoolSize(),
this.executor.getActiveCount(),
this.executor.getCompletedTaskCount(),
this.executor.getTaskCount(),
this.executor.isShutdown(),
this.executor.isTerminated()));
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Ecco un esempio di implementazione del pool di thread utilizzando ThreadPoolExecutor.
package com.journaldev.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WorkerPool {
public static void main(String args[]) throws InterruptedException{
// Implementazione di RejectedExecutionHandler
RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
// Ottieni l'implementazione di ThreadFactory da utilizzare
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// creazione del ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), threadFactory, rejectionHandler);
// avvia il thread di monitoraggio
MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
// invia il lavoro al pool di thread
for(int i=0; i<10; i++){
executorPool.execute(new WorkerThread("cmd"+i));
}
Thread.sleep(30000);
// arresta il pool
executorPool.shutdown();
// arresta il thread di monitoraggio
Thread.sleep(5000);
monitor.shutdown();
}
}
Si noti che durante l’inizializzazione del ThreadPoolExecutor, manteniamo la dimensione del pool iniziale a 2, la dimensione massima del pool a 4 e la dimensione della coda di lavoro a 2. Quindi, se ci sono 4 attività in esecuzione e vengono inviate altre attività, la coda di lavoro conterrà solo 2 di esse e il resto verrà gestito da RejectedExecutionHandlerImpl
. Ecco l’output del programma precedente che conferma quanto sopra.
pool-1-thread-1 Start. Command = cmd0
pool-1-thread-4 Start. Command = cmd5
cmd6 is rejected
pool-1-thread-3 Start. Command = cmd4
pool-1-thread-2 Start. Command = cmd1
cmd7 is rejected
cmd8 is rejected
cmd9 is rejected
[monitor] [0/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-4 End.
pool-1-thread-1 End.
pool-1-thread-2 End.
pool-1-thread-3 End.
pool-1-thread-1 Start. Command = cmd3
pool-1-thread-4 Start. Command = cmd2
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-1 End.
pool-1-thread-4 End.
[monitor] [4/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
Nota il cambiamento nel conteggio delle attività attive, completate e totali completate dell’esecutore. Possiamo invocare il metodo shutdown() per terminare l’esecuzione di tutte le attività inviate e terminare il pool di thread. Se si desidera pianificare un’attività da eseguire con ritardo o periodicamente, è possibile utilizzare la classe ScheduledThreadPoolExecutor. Leggi di più su di loro su Java Schedule Thread Pool Executor.