Le pool de threads Java gère le pool de threads travailleurs. Il contient une file d’attente qui conserve les tâches en attente d’exécution. Nous pouvons utiliser ThreadPoolExecutor
pour créer un pool de threads en Java. Le pool de threads Java gère la collection de threads Runnable. Les threads travailleurs exécutent les threads Runnable de la file d’attente. java.util.concurrent.Executors fournit des méthodes de création et de support pour l’interface java.util.concurrent.Executor afin de créer le pool de threads en Java. Executors est une classe utilitaire qui fournit également des méthodes utiles pour travailler avec ExecutorService, ScheduledExecutorService, ThreadFactory et les classes Callable grâce à diverses méthodes de fabrique. Écrivons un programme simple pour expliquer son fonctionnement. Tout d’abord, nous devons avoir une classe Runnable, nommée 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;
}
}
Exemple de ExecutorService
Voici la classe de programme de test SimpleThreadPool.java
, où nous créons un pool de threads fixe à partir du 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");
}
}
Dans le programme ci-dessus, nous créons un pool de threads de taille fixe de 5 threads travailleurs. Ensuite, nous soumettons 10 tâches à ce pool, puisque la taille du pool est de 5, il commencera à travailler sur 5 tâches et les autres tâches seront en état d’attente. Dès qu’une des tâches est terminée, une autre tâche de la file d’attente sera récupérée par le thread travailleur et exécutée. Voici la sortie du programme ci-dessus.
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
La sortie confirme qu’il y a cinq fils dans le pool nommés de « pool-1-thread-1 » à « pool-1-thread-5 » et qu’ils sont responsables de l’exécution des tâches soumises au pool.
Exemple de ThreadPoolExecutor
La classe Executors fournit une implémentation simple de ExecutorService en utilisant ThreadPoolExecutor, mais ThreadPoolExecutor offre bien plus de fonctionnalités que cela. Nous pouvons spécifier le nombre de fils qui seront actifs lorsque nous créons une instance de ThreadPoolExecutor et nous pouvons limiter la taille du pool de fils et créer notre propre implémentation de RejectedExecutionHandler pour gérer les tâches qui ne peuvent pas être placées dans la file d’attente des travailleurs. Voici notre implémentation personnalisée de l’interface 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
fournit plusieurs méthodes grâce auxquelles nous pouvons connaître l’état actuel de l’exécuteur, la taille du pool, le nombre de fils actifs et le nombre de tâches. J’ai donc un fil de surveillance qui affichera les informations sur l’exécuteur à un certain intervalle de temps.
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();
}
}
}
}
Voici un exemple d’implémentation de pool de fils utilisant 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{
Implémentation de RejectedExecutionHandler
RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
Obtenez l'implémentation ThreadFactory à utiliser
ThreadFactory threadFactory = Executors.defaultThreadFactory();
création du ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), threadFactory, rejectionHandler);
démarrer le thread de surveillance
MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
soumettre un travail au pool de threads
for(int i=0; i<10; i++){
executorPool.execute(new WorkerThread("cmd"+i));
}
Thread.sleep(30000);
arrêter le pool
executorPool.shutdown();
arrêter le thread de surveillance
Thread.sleep(5000);
monitor.shutdown();
}
}
Remarquez que lors de l’initialisation du ThreadPoolExecutor, nous conservons la taille initiale du pool à 2, la taille maximale du pool à 4 et la taille de la file d’attente à 2. Ainsi, s’il y a 4 tâches en cours d’exécution et que d’autres tâches sont soumises, la file d’attente ne retiendra que 2 d’entre elles et les autres seront gérées par RejectedExecutionHandlerImpl
. Voici la sortie du programme ci-dessus qui confirme l’énoncé précédent.
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
Remarquez le changement dans le compte actif, terminé et total des tâches terminées de l’exécuteur. Nous pouvons invoquer la méthode shutdown() pour terminer l’exécution de toutes les tâches soumises et mettre fin au pool de threads. Si vous souhaitez planifier l’exécution d’une tâche avec un délai ou périodiquement, vous pouvez utiliser la classe ScheduledThreadPoolExecutor. En savoir plus à leur sujet sur Java Schedule Thread Pool Executor.