Maîtriser le multithreading Java : Programmation concurrente efficace
Java est un langage de programmation multi-thread, ce qui signifie qu'on peut développer des programmes multi-threads en Java. Un programme multi-thread contient deux parties ou plus qui s'exécutent simultanément, chaque partie gérant une tâche différente au même moment, ce qui permet une utilisation optimale des ressources, surtout sur les ordinateurs multi-cœurs.
Par définition, le multitâche correspond au partage de ressources de traitement (CPU) entre plusieurs processus. Le multi-threading étend cette idée aux applications en subdivisant des opérations spécifiques au sein d'une même application en threads individuels. Chaque thread peut s'exécuter en parallèle. L'OS répartit le temps processeur non seulement entre différentes applications, mais aussi entre chaque thread d'une application.
Le multi-threading permet d'écrire des programmes où plusieurs activités progressent simultanément.
Cycle de vie d'un thread
Un thread traverse plusieurs états au cours de son cycle de vie : naissance, démarrage, exécution, puis terminaison. Le diagramme ci-dessous illustre ce cycle complet.
Nouveau (New) : Le thread vient d'être créé mais n'a pas encore démarré. On l'appelle aussi thread né.
Exécutable (Runnable) : Après l'appel à
start(), le thread devient exécutable et est considéré comme en cours d'exécution.En attente (Waiting) : Le thread attend qu'un autre thread effectue une action. Il repasse à l'état exécutable seulement quand un autre thread le notifie.
Attente temporisée (Timed Waiting) : Le thread attend pendant un intervalle défini. Il redevient executable à l'expiration du délai ou sur notification.
Terminé (Dead) : Le thread a terminé sa tâche ou a été arrêté.
Priorités des threads
Chaque thread Java possède une priorité qui aide l'OS à déterminer l'ordre d'ordonnancement. Les priorités vont de MIN_PRIORITY (1) à MAX_PRIORITY (10). La priorité par défaut est NORM_PRIORITY (5).
Les threads de priorité plus élevée sont théoriquement alloués en premier, mais cela ne garantit pas l'ordre d'exécution et reste très dépendant de la plateforme.
Créer un thread en implémentant Runnable
Si votre classe doit s'exécuter comme un thread, implémentez l'interface Runnable en trois étapes :
Étape 1 : Implémenter run()
La méthode run() est le point d'entrée du thread. Placez-y votre logique métier.
public void run()
Étape 2 : Instancier Thread
Thread(Runnable threadObj, String threadName)
threadObj est une instance implémentant Runnable, threadName nomme le nouveau thread.
Étape 3 : Démarrer avec start()
void start()
Cette méthode invoque run() dans un nouveau thread d'exécution.
Exemple complet
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo(String name) {
threadName = name;
System.out.println("Création de " + threadName);
}
public void run() {
System.out.println("Exécution de " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrompu.");
}
System.out.println("Thread " + threadName + " terminé.");
}
public void start() {
System.out.println("Démarrage de " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String[] args) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}
Résultat typique
Création de Thread-1 Démarrage de Thread-1 Création de Thread-2 Démarrage de Thread-2 Exécution de Thread-1 Thread: Thread-1, 4 Exécution de Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 terminé. Thread Thread-2 terminé.
Créer un thread en étendant Thread
Alternative : créer une classe qui étend Thread. Cette approche offre plus de flexibilité grâce aux méthodes de la classe Thread.
Étape 1 : Surcharger run()
public void run()
Étape 2 : Appeler start()
void start()
Exemple
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo(String name) {
threadName = name;
System.out.println("Création de " + threadName);
}
public void run() {
System.out.println("Exécution de " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrompu.");
}
System.out.println("Thread " + threadName + " terminé.");
}
public void start() {
System.out.println("Démarrage de " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String[] args) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}
Méthodes importantes de la classe Thread
| N° | Méthode et description |
|---|---|
| 1 | public void start() - Démarre le thread dans un chemin d'exécution séparé, puis invoque run(). |
| 2 | public void run() - Point d'entrée si le thread a été créé avec un Runnable. |
| 3 | public final void setName(String name) - Change le nom du thread. |
| 4 | public final void setPriority(int priority) - Définit la priorité (1 à 10). |
| 5 | public final void setDaemon(boolean on) - Marque le thread comme démon. |
| 6 | public final void join(long millisec) - Attend la fin d'un autre thread. |
| 7 | public void interrupt() - Interrompt un thread bloqué. |
| 8 | public final boolean isAlive() - Retourne true si le thread est vivant. |
Méthodes statiques (agissent sur le thread courant) :
| N° | Méthode et description |
|---|---|
| 1 | public static void yield() - Cède le processeur aux threads de même priorité. |
| 2 | public static void sleep(long millisec) - Bloque le thread courant. |
| 3 | public static boolean holdsLock(Object x) - Vérifie si le thread courant détient le verrou sur l'objet. |
| 4 | public static Thread currentThread() - Retourne le thread courant. |
| 5 | public static void dumpStack() - Affiche la pile d'appels (debug). |
Exemple d'utilisation
// File Name : DisplayMessage.java
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) { this.message = message; }
public void run() { while(true) { System.out.println(message); } }
}
// File Name : GuessANumber.java
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) { this.number = number; }
public void run() {
int counter = 0, guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " devine " + guess);
counter++;
} while (guess != number);
System.out.println("** Correct ! " + this.getName() + " en " + counter + " essais.**");
}
}
// File Name : ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String[] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
thread2.start();
Thread thread3 = new GuessANumber(27);
thread3.start();
try { thread3.join(); } catch (InterruptedException e) { }
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() se termine...");
}
}
Concepts avancés du multithreading Java
Pour aller plus loin, maîtrisez ces concepts essentiels :
- Synchronisation des threads
- Communication inter-threads
- Gestion des interblocages (deadlocks)
- Opérations principales sur les threads
Java
- Maîtriser les opérateurs Java : Types, syntaxe et exemples pratiques
- Commentaires Java : Types, utilisation et bonnes pratiques
- Maîtriser la boucle for améliorée en Java : Syntaxe, exemples et bonnes pratiques
- Maîtriser les chaînes de caractères en Java : Création, méthodes et bonnes pratiques
- Maîtriser les interfaces Java : concepts, implémentation et bonnes pratiques
- Maîtriser les classes internes anonymes en Java : définition, syntaxe et exemples pratiques
- Maîtriser le try-with-resources Java : gestion automatique des ressources expliquée
- Annotations Java expliquées : types, placement et exemples pratiques
- Assertions Java : guide pratique pour détecter les bugs et assurer la fiabilité du code