Pourquoi pourriez-vous avoir besoin d'un ExecutorService pour 1 thread ?

Vous pouvez utiliser la méthode Executors.newSingleThreadExecutor pour créer un ExecutorService avec un pool qui inclut un seul thread. La logique du pool est la suivante :

  • Le service exécute une seule tâche à la fois.
  • Si nous soumettons N tâches pour exécution, toutes les tâches N seront exécutées les unes après les autres par le thread unique.
  • Si le thread est interrompu, un nouveau thread sera créé pour exécuter toutes les tâches restantes.

Imaginons une situation où notre programme nécessite les fonctionnalités suivantes :

Nous devons traiter les demandes des utilisateurs dans les 30 secondes, mais pas plus d'une demande par unité de temps.

Nous créons une classe de tâche pour traiter une requête utilisateur :


class Task implements Runnable {
   private final int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException ignored) {
       }
       System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
   }
}
    

La classe modélise le comportement de traitement d'une demande entrante et affiche son numéro.

Ensuite, dans la méthode principale , nous créons un ExecutorService pour 1 thread, que nous utiliserons pour traiter séquentiellement les demandes entrantes. Étant donné que les conditions de la tâche stipulent "dans les 30 secondes", nous ajoutons une attente de 30 secondes, puis arrêtons de force ExecutorService .


public static void main(String[] args) throws InterruptedException {
   ExecutorService executorService = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 1_000; i++) {
       executorService.execute(new Task(i));
   }
   executorService.awaitTermination(30, TimeUnit.SECONDS);
   executorService.shutdownNow();
}
    

Après le démarrage du programme, la console affiche des messages sur le traitement des requêtes :

Requête traitée n°0 sur le thread id=16
Requête traitée n°1 sur le thread id=16
Requête traitée n°2 sur le thread id=16

Requête traitée n°29 sur le thread id=16

Après avoir traité les requêtes pendant 30 secondes, executorService appelle la méthode shutdownNow() , qui arrête la tâche en cours (celle en cours d'exécution) et annule toutes les tâches en attente. Après cela, le programme se termine avec succès.

Mais tout n'est pas toujours aussi parfait, car notre programme pourrait facilement avoir une situation où l'une des tâches récupérées par le seul thread de notre pool fonctionne de manière incorrecte et même termine notre thread. Nous pouvons simuler cette situation pour comprendre comment executorService fonctionne avec un seul thread dans ce cas.

Pour ce faire, pendant l'exécution d'une des tâches, nous terminons notre thread en utilisant la méthode non sécurisée et obsolète Thread.currentThread().stop() . Nous faisons cela intentionnellement pour simuler la situation où l'une des tâches termine le thread.

Nous allons changer la méthode run dans la classe Task :


@Override
public void run() {
   try {
       Thread.sleep(1000);
   } catch (InterruptedException ignored) {
   }

   if (taskNumber == 5) {
       Thread.currentThread().stop();
   }

   System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
    

Nous allons interrompre la tâche #5.

Voyons à quoi ressemble la sortie avec le thread interrompu à la fin de la tâche n° 5 :

Requête traitée n°0 sur le thread id=16
Requête traitée n°1 sur le thread id=16
Requête traitée n°2 sur le thread id=16
Requête traitée n°3 sur le thread id=16
Requête traitée n°4 sur le thread id=16
Requête traitée n°6 sur thread id=17
Requête traitée n°7 sur thread id=17

Requête traitée n°29 sur thread id=17

On voit qu'après l'interruption du thread à la fin de la tâche 5, les tâches commencent à s'exécuter dans un thread dont l'identifiant est 17, alors qu'elles avaient été précédemment exécutées sur le thread dont l'identifiant est 16. Et parce que notre pool a un thread unique, cela ne peut signifier qu'une chose : executorService a remplacé le thread arrêté par un nouveau et a continué à exécuter les tâches.

Ainsi, nous devrions utiliser newSingleThreadExecutor avec un pool à un seul thread lorsque nous voulons traiter les tâches de manière séquentielle et une seule à la fois, et que nous voulons continuer à traiter les tâches de la file d'attente indépendamment de l'achèvement de la tâche précédente (par exemple, le cas où un de nos tâches tue le fil).

ThreadFactory

En parlant de créer et de recréer des threads, nous ne pouvons pas nous empêcher de mentionnerThreadFactory.

UNThreadFactoryest un objet qui crée de nouveaux threads à la demande.

Nous pouvons créer notre propre usine de création de threads et en transmettre une instance à la méthode Executors.newSingleThreadExecutor(ThreadFactory threadFactory) .


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
Nous redéfinissons la méthode de création d'un nouveau thread, en passant un nom de thread au constructeur.

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
Nous avons changé le nom et la priorité du fil créé.

Nous voyons donc que nous avons 2 méthodes Executors.newSingleThreadExecutor surchargées. Un sans paramètre et un second avec un paramètre ThreadFactory .

À l'aide d'un ThreadFactory , vous pouvez configurer les threads créés selon vos besoins, par exemple en définissant des priorités, en utilisant des sous-classes de threads, en ajoutant un UncaughtExceptionHandler au thread, etc.