Le multithreading permet d'exécuter plusieurs tâches en parallèle, optimisant ainsi les performances des applications Java. Depuis Java 1.0, le langage offre un support natif robuste. Les évolutions, notamment depuis Java 5, ont enrichi les approches pour structurer efficacement le code multithreadé.
Dans cet article, nous explorons quatre méthodes principales avec des exemples concrets pour vous aider à choisir la meilleure pour votre projet.

La classe Thread de Java peut être étendue pour implémenter la méthode run(), où réside la logique de la tâche. Instanciez la classe et appelez start() pour lancer le thread, qui s'exécute jusqu'à son terme ou une exception.

Voici un exemple simple simulant une opération longue via un sommeil :
public class MyThread extends Thread {
private int sleepFor;
public MyThread(int sleepFor) {
this.sleepFor = sleepFor;
}
@Override
public void run() {
System.out.printf("Le thread [%s] démarre\n", Thread.currentThread().toString());
try {
Thread.sleep(this.sleepFor);
} catch (InterruptedException ex) {
// Gestion interruption
}
System.out.printf("[%s] fin du thread\n", Thread.currentThread().toString());
}
}Créez et lancez l'instance :
MyThread worker = new MyThread(sleepFor);
worker.start();
System.out.printf("[%s] fil principal\n", Thread.currentThread().toString());Sortie typique :
[Thread[main,5,main]] fil principal
[Thread[Thread-0,5,main]] Le thread démarre
[Thread[Thread-0,5,main]] fin du threadLe thread principal attend la fin du worker avant de terminer.
L'interface Runnable offre une alternative flexible : implémentez run() sans étendre Thread, préservant la possibilité d'hériter d'une autre classe.

Exemple :
public class MyThread2 implements Runnable {
// Même logique que précédemment
}Avantage clé : compatibilité avec l'héritage. Par exemple, une classe Papaya étendant Fruit peut implémenter Runnable :
public class Fruit { /* ... */ }
public class Papaya extends Fruit implements Runnable {
@Override
public void run() {
// Tâche longue
}
}Lancement :
Papaya papaya = new Papaya();
Thread thread = new Thread(papaya);
thread.start();
Depuis Java 5, ExecutorService abstrait la gestion des threads via des pools, évitant la création excessive de threads.
Pour 100 tâches, un pool de 10 threads réutilise les ressources efficacement.
Exemple avec un exécuteur mono-thread :
ExecutorService esvc = Executors.newSingleThreadExecutor();
Runnable worker = new MyThread2(sleepFor);
Future<?> future = esvc.submit(worker);
System.out.printf("[%s] fil principal\n", Thread.currentThread().toString());
future.get();
esvc.shutdown();Important : Arrêtez toujours l'ExecutorService.
Callable étend Runnable en permettant un retour de valeur via call() et la gestion d'exceptions.
Exemple avec Mango étendant Fruit :
public class Mango extends Fruit implements Callable<Integer> {
@Override
public Integer call() {
// Calcul coûteux
return new Integer(0);
}
}Lancement :
ExecutorService esvc = Executors.newSingleThreadExecutor();
Callable<Integer> worker = new Mango(sleepFor);
Future<Integer> future = esvc.submit(worker);
System.out.printf("[%s] fil principal\n", Thread.currentThread().toString());
System.out.println("Tâche retournée : " + future.get());
esvc.shutdown();Récapitulatif :
Quelle approche utiliserez-vous ? Partagez en commentaires !
[]