La classe Thread en Java : Principe et usage fondamental

Programmation multithread en Java

Système d'exploitation et threads Java

Les threads sont un concept du système d'exploitation. Le noyau du système implémente ce mécanisme et fournit des API aux couches utilisateur (par exemple, la bibliothèque pthread sous Linux).

En Java, la bibliothèque standard propose une classe Thread qui représente un thread. On peut considérer qu'il s'agit d'une abstraction et encapsulation supplémentaire des API fournies par le système d'exploitation, c'est-à-dire que les opérations sur les threads dépendent des API du système.

Utilisation simple du multithreading

Création basique d'un nouveau thread


class MonFil extends Thread {
    @Override
    public void run() {
        System.out.println("Salut tout le monde!");
    }
}

public class DemoThread {
    public static void main(String[] args) {
        MonFil fil = new MonFil();
        fil.start();
    }
}

Ce code implique deux threads : le thread principal associé à la méthode main (un processus doit avoir au moins un thread) et un nouveau thread créé par fil.start(). Le nouveau thread appelle fil.run(), et une fois l'exécution terminnée, ce thread est détruit. Le thread principal se termine également car start() est la dernière instruction dans main.

Comprendre chaque thread comme un flux d'exécution indépendant


class MonFil extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("Salut Fil!");
        }
    }
}

public class DemoThread1 {
    public static void main(String[] args) {
        Thread fil = new MonFil();
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
        }
    }
}

Les deux messages s'impriment alternativement. Le thread principal et le nouveau thread s'exécutent simultanément via une planification rapide, produisant une sortie alternée sur la même console. Notez que si c'était un thread unique, seul un message serait visible.

La classe Thread

Constructeurs courants

Thread() : Crée un objet thread.

Thread t1 = new Thread();

Thread(Runnable cible) : Crée un thread avec un objet Runnable.

Thread t2 = new Thread(new MonRunnable());

Thread(String nom) : Crée un thread avec un nom spécifié.

Thread t3 = new Thread("MonFil");

Thread(Runnable cible, String nom) : Crée un thread avec un Runnable et un nom.

Thread t4 = new Thread(new MonRunnable(), "MonFil");

Propriétés et méthodes courantes

ID – getID() : Identifiant unique du thread.


public class DemoThread2 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.getId());
        System.out.println("Salut Main!");
    }
}

Nom – getName() : Nom utilisé par les outils de débogage.


public class DemoThread3 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.getName());
        System.out.println("Salut Main!");
    }
}

État – getState() : Représente la situation actuelle du thread.


public class DemoThread4 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.getState());
        System.out.println("Salut Main!");
    }
}

Priorité – getPriority() : Fournit une recommandation au système ; une priorité plus élevée peut augmenter les chances de planification.


public class DemoThread5 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.getPriority());
        System.out.println("Salut Main!");
    }
}

Thread d'arrière-plan – isDaemon() : Si vrai, le thread est d'arrière-plan et n'empêche pas la fin du processus Java ; sinon, il est de premier plan. Les threads de premier plan sont par défaut, modifiables via setDaemon().


public class DemoThread6 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.isDaemon());
        System.out.println("Salut Main!");
    }
}

Actif – isAlive() : Détermine si le thread est en cours d'exécution ou prêt à s'exécuter. La valeur dépend du moment de l'appel.


class MonFil extends Thread {
    @Override
    public void run() {
        System.out.println("run=" + this.isAlive());
    }
}

public class DemoThread7 {
    public static void main(String[] args) throws InterruptedException {
        Thread fil = new MonFil();
        System.out.println("Debut=" + fil.isAlive());
        fil.start();
        System.out.println("Fin=" + fil.isAlive());
    }
}

Interrompu – isInterrupted() : Indique si le thread a été interrompu.


public class DemoThread8 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        System.out.println(fil.isInterrupted());
        System.out.println("Salut Main!");
    }
}

Utilisation fondamentale de Thread

Création de threads

Il existe plusieurs méthodes pour créer des threads.

1. En étendant Thread et en redéfinissant run


class MonFil extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("Salut Fil!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DemoCreation1 {
    public static void main(String[] args) {
        Thread fil = new MonFil();
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. En implémentant Runnable et en redéfinissant run


class MonRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("Salut Fil!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DemoCreation2 {
    public static void main(String[] args) {
        MonRunnable runnable = new MonRunnable();
        Thread fil = new Thread(runnable);
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3. En utilisant une classe interne anonyme avec Thread


public class DemoCreation3 {
    public static void main(String[] args) {
        Thread fil = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Salut Fil!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4. En utilisant une classe interne anonyme avec Runnable


public class DemoCreation4 {
    public static void main(String[] args) {
        Thread fil = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Salut Fil!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5. Avec une expression lambda (approche recommandée)


public class DemoCreation5 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            while (true) {
                System.out.println("Salut Fil!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Les première et deuxième méthodes n'ont pas de différence essentielle ; la première utilise le méthode run de Thread, tandis que la seconde utilise l'interface Runnable. Les classes internes anonymes et les expressions lambda offrent une syntaxe plus concise.

Démarrage d'un thread – start()

Appeler start() crée réellement un nouveau thread et déclenche l'exécution de la méthode run(). Si run() est appelé directement, le code s'exécute dans le thread actuel sans en créer un nouveau. Une fois démarré, un thread ne peut pas être redémarré avec start(), sinon une exception IllegalThreadStateException est levée.

Sommeil d'un thread – sleep()

sleep() est une méthode statique qui suspend le thread actuel pendant la durée spécifiée en millisecondes. Le temps réel de sommeil peut être supérieur. Si le thread est interrompu pendant le sommeil, une exception InterruptedException est lancée.


class MonFil extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("Salut Fil!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DemoSleep {
    public static void main(String[] args) {
        Thread fil = new MonFil();
        fil.start();
        while (true) {
            System.out.println("Salut Main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

L'alternance de sortie n'est pas strictement ordonnée en raison de la planification aléatoire du système.

Référence du thread courant – currentThread()

Retourne une référence au thread en cours d'exécution.


public class DemoCurrentThread {
    public static void main(String[] args) {
        Thread fil = Thread.currentThread();
        System.out.println(fil.getName());
    }
}

Interruption d'un thread – interrupt()

Un thread peut être terminé en faisant sortir sa méthode run(). Deux approches courantes :

1. Utiliser un indicateur de fin


public class DemoInterruption1 {
    public static boolean fin = false;

    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            while (!fin) {
                System.out.println("Salut Fil!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Fil terminé!");
        });
        fil.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        fin = true;
    }
}

L'indicatueur fin doit être une variable membre pour éviter les problèmes de capture de variables avec les lambdas.

2. Utiliser la méthode interrupt()


public class DemoInterruption2 {
    public static void main(String[] args) {
        Thread fil = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Salut Fil!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break; // Sortir de la boucle après l'interruption
                }
            }
        });
        fil.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        fil.interrupt();
    }
}

interrupt() a deux effets : définit un indicatueur et réveille le thread s'il est en état bloquant (comme sleep()), en lançant une exception. Lors du réveil, l'indicatueur est réinitialisé, donc un break dans le bloc catch est nécessaire pour arrêter proprement le thread.

Attente d'un thread – join()

join() permet de contrôler l'ordre de terminaison des threads. Si un thread A appelle B.join(), A sera en état bloquant jusqu'à la fin de B.

join() sans paramètre


public class DemoJoin1 {
    public static void main(String[] args) throws InterruptedException {
        Thread fil = new Thread(() -> {
            System.out.println("Salut Fil");
        });
        fil.start();
        fil.join();
        System.out.println("Salut Main");
    }
}

join() avec paramètre de temps


public class DemoJoin2 {
    public static void main(String[] args) throws InterruptedException {
        Thread fil = new Thread(() -> {
            while (true) {
                System.out.println("Salut Fil");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        fil.start();
        fil.join(3000); // Attente maximale de 3 secondes
        System.out.println("Salut Main");
    }
}

La version avec paramètre définit un délai maximal d'attente ; si le thread cible n'a pas terminé dans ce délai, l'exécution continue.

Étiquettes: Java Thread multithreading Programmation concurrente Interruption de thread

Publié le 4 juin à 18h03