Implémentation d'un exécutable vs extension d'un thread

1. Introduction

«Dois-je implémenter un Runnable ou étendre la classe Thread »? est une question assez courante.

Dans cet article, nous verrons quelle approche a le plus de sens dans la pratique et pourquoi.

2. Utilisation de Thread

Définissons d'abord une classe SimpleThread qui étend Thread :

public class SimpleThread extends Thread { private String message; // standard logger, constructor @Override public void run() { log.info(message); } }

Voyons également comment nous pouvons exécuter un thread de ce type:

@Test public void givenAThread_whenRunIt_thenResult() throws Exception { Thread thread = new SimpleThread( "SimpleThread executed using Thread"); thread.start(); thread.join(); }

Nous pouvons également utiliser un ExecutorService pour exécuter le thread:

@Test public void givenAThread_whenSubmitToES_thenResult() throws Exception { executorService.submit(new SimpleThread( "SimpleThread executed using ExecutorService")).get(); }

C'est beaucoup de code pour exécuter une seule opération de journal dans un thread séparé.

Notez également que SimpleThread ne peut étendre aucune autre classe , car Java ne prend pas en charge l'héritage multiple.

3. Implémentation d'un exécutable

Maintenant, créons une tâche simple qui implémente l' interface java.lang.Runnable :

class SimpleRunnable implements Runnable { private String message; // standard logger, constructor @Override public void run() { log.info(message); } }

Le SimpleRunnable ci-dessus est juste une tâche que nous voulons exécuter dans un thread séparé.

Il existe différentes approches que nous pouvons utiliser pour l'exécuter; l'un d'eux consiste à utiliser la classe Thread :

@Test public void givenRunnable_whenRunIt_thenResult() throws Exception { Thread thread = new Thread(new SimpleRunnable( "SimpleRunnable executed using Thread")); thread.start(); thread.join(); }

Nous pouvons même utiliser un ExecutorService :

@Test public void givenARunnable_whenSubmitToES_thenResult() throws Exception { executorService.submit(new SimpleRunnable( "SimpleRunnable executed using ExecutorService")).get(); }

Nous pouvons en savoir plus sur ExecutorService ici.

Puisque nous implémentons maintenant une interface, nous sommes libres d'étendre une autre classe de base si nécessaire.

À partir de Java 8, toute interface qui expose une seule méthode abstraite est traitée comme une interface fonctionnelle, ce qui en fait une cible d'expression lambda valide.

Nous pouvons réécrire le code exécutable ci-dessus en utilisant une expression lambda :

@Test public void givenARunnableLambda_whenSubmitToES_thenResult() throws Exception { executorService.submit( () -> log.info("Lambda runnable executed!")); }

4. Exécutable ou Thread ?

En termes simples, nous encourageons généralement l'utilisation de Runnable over Thread :

  • Lors de l'extension de la classe Thread , nous ne remplaçons aucune de ses méthodes. Au lieu de cela, nous remplaçons la méthode de Runnable ( que Thread implémente ) . Il s'agit d'une violation flagrante du principe IS-A Thread
  • Créer une implémentation de Runnable et la transmettre à la classe Thread utilise la composition et non l'héritage - ce qui est plus flexible
  • Après avoir étendu la classe Thread , nous ne pouvons étendre aucune autre classe
  • À partir de Java 8, les exécutables peuvent être représentés sous forme d'expressions lambda

5. Conclusion

Dans ce rapide tutoriel, nous avons vu comment l'implémentation de Runnable est généralement une meilleure approche que l'extension de la classe Thread .

Le code de ce message se trouve à l'adresse over sur GitHub.