FAQ Langage JavaConsultez toutes les FAQ
Nombre d'auteurs : 42, nombre de questions : 297, dernière mise à jour : 19 septembre 2017 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.
Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- Qu'est-ce qu'un thread ?
- Pourquoi toutes les méthodes de la classe Thread sont-elles marquées « deprecated » ?
- Comment créer un thread ?
- Comment démarrer un thread ?
- Comment terminer un thread sans la méthode stop() ?
- Comment obtenir le thread courant ?
- Comment faire une « pause » dans mon application ?
- Comment changer la priorité d'exécution d'un thread ?
- Pourquoi est-ce que mon programme ne s'arrête pas à la fin du main() ?
- Comment créer un thread deamon ?
- Qu'est-ce que le double-check locking ?
- Comment avoir un Singleton sûr en environnement multi-thread ?
Un thread désigne un « fil d'exécution » dans le programme ; c'est-à-dire une suite linéaire et continue d'instructions qui sont exécutées séquentiellement les unes après les autres. En fait, le langage Java est multi-thread, c'est-à-dire qu'il peut faire cohabiter plusieurs fils d’exécution de façon indépendante.
L'utilisation des threads est très fréquente dans la programmation réseau. Un client FTP peut télécharger plusieurs fichiers, naviguer sur plusieurs serveurs en même temps, chaque connexion étant gérée par un thread différent. De même, un serveur FTP, doit généralement pouvoir traiter plusieurs clients connectés en même temps ; sinon chaque client devrait patiemment attendre son tour, ce qui rendrait l'utilisation d'un tel serveur très pénible.
Les threads sont également très utilisés dans les applications disposant d'interfaces graphiques. Par exemple, dans un traitement de texte, un thread s'occupe de ce que saisit l'utilisateur, un autre est en charge de détecter les fautes d'orthographe et de grammaire, un autre se charge de l'impression du document dans une tâche de fond, etc.
Les programmes Java ont tous au moins un thread d’exécution qui est démarré lorsque la méthode main() qui lance le programme est invoquée. De plus, la JVM peut générer des threads supplémentaires pour faire tourner le ramasse-miettes en tâche de fond. Les environnements graphiques fournis avec la JVM (AWT, Swing, JavaFX) disposent tous d'un ou plusieurs autres threads graphiques et/ou évènementiels qui se lancent à l'initialisation de ces environnements.
Pour mieux comprendre l'utilisation des threads, vous pouvez vous pencher sur ces deux tutoriels :
- Programmation des Threads en Java, première partie par Valère Viandier.
- Sémaphores et Mutex en Delphi et Java par Mathieu Dalbint.
Depuis le JDK 5, Java dispose aussi du package java.util.concurrent qui permet une meilleure gestion de la concurrence.
- Comment créer un thread ?
- Tutoriel : Programmation des Threads en Java par Valère Viandier ;
- Tutoriel : Sémaphores et Mutex en Delphi et Java par Mathieu Dalbin ;
- Package java.util.concurrent
Toutes ces méthodes ont été marquées deprecated (dépréciées ou obsolètes), car, sous certaines conditions et sans qu'il y ait d'erreur de conception, l'application peut se bloquer. Typiquement, lorsqu'un thread est tué avec la méthode stop(), il n'est pas possible de savoir ce qu'il était en train de faire, il est donc possible qu'il soit arrêté au milieu d'une modification d'un objet ; cet objet est donc laissé dans un état incohérent. Des problèmes similaires peuvent se produire avec les méthodes suspend() et resume().
Créer un thread est très simple en Java. Plusieurs solutions s'offrent à vous.
java.lang.Thread
Vous pouvez créer une nouvelle classe qui dérive de la classe java.lang.Thread. Il suffit ensuite de redéfinir la méthode run(). C'est cette méthode que le thread va exécuter :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 | Thread tache = new Thread() { @Override public void run() { // Code à exécuter dans ce thread. [...] } } |
java.lang.Runnable
Si vous ne souhaitez pas faire une classe dédiée à la gestion du processus, vous pouvez simplement implémenter l'interface java.lang.Runnable et définir la méthode run(). Ensuite il suffit de créer un objet java.lang.Thread en lui passant la classe en paramètre :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | Runnable code = new Runnable() { @Override public void run() { // Code à exécuter. [...] } } // On place le code dans un nouveau thread. Thread tache = new Thread(code); |
Autres
Des frameworks graphiques tels que Swing ou JavaFX disposent de leurs propres outils pour créer des threads ou des tâches de fond. Pensez à vous référer à la documentation ou aux didacticiels appropriés pour savoir quelles classes utiliser dans ces environnements.
- Comment démarrer un thread ?
- Comment terminer un thread sans la méthode stop() ?
- Comment lancer un traitement long ? (Swing) ;
- Comment effectuer une tâche de fond ? (JavaFX) ;
- Tutoriel : Programmation des Threads en Java par Valère Viandier ;
- Tutoriel : SwingWorker (Java SE 6) par Romain Vimont ;
- Tutoriel : Exécution d'une tâche de fond en JavaFX par Fabrice Bouyé.
Pour lancer l'exécution d'un thread vous devez exécuter la méthode start().
Code Java : | Sélectionner tout |
1 2 | Thread tache = [...] tache.start(); |
Avertissement : vous ne devez en aucun cas exécuter vous-même la méthode run(), car l'exécution se déroulerait alors dans le processus courant !
La méthode stop() de la classe java.lang.Thread étant dépréciée, il ne faut pas l'utiliser.
Méthode interrupt()
La manière la plus simple d’arrêter un thread est d'invoquer la méthode interrupt() de la classe java.lang.Thread. Cette méthode a plusieurs effets possibles :
- si votre thread était en train d'attendre ou de dormir (wait(), join(), sleep(), etc.), une exception de type java.lang.InterruptedException sera générée dans le code à l'endroit où cette attente a lieu ;
- si votre thread était en train d'effectuer un appel IO bloquant sur un canal, ce dernier sera fermé et une exception de type java.nio.channels.ClosedByInterruptException sera générée dans le code à l'endroit où cette opération a lieu ;
- si votre thread est bloqué dans un Selector, votre thread sera placé dans l’état interrupt et l’opération de sélection retournera immédiatement comme si la méthode wakeup() avait été invoquée ;
- sinon, votre thread sera placé dans l’état interrupt et ses méthodes interrupted() et isInterrupted() renverront la valeur true. Vous pouvez donc alors tester la valeur de retour de cette méthode depuis votre code pour terminer la tâche prématurément ou faire des sauvegardes, fermetures de session, etc. si besoin avant de la quitter.
Dans le code appelant :
Code Java : | Sélectionner tout |
1 2 3 4 | Thread tache = [...] tache.start(); [...] tache.interrupt(); |
Et dans la tâche :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override public void run() { try { [...] // Si interrupt() est invoqué alors que la tâche exécute cette portion du code, il suffit de tester l’état du thread. if (isInterrupted()) { return; } [...] // Si interrupt() est invoqué alors que la tâche dort suite à cet appel, une exception est générée. sleep(500); [...] } (catch (InterruptedException ex) { // Traitement de l'erreur. } } |
Attention :
- interrupted() est une méthode statique qui agit sur le thread courant et remet à false l’état interrupt du thread lors de son invocation ;
- isInterrupted() est une méthode d'instance. Elle ne remet pas à false l’état interrupt du thread lors de son invocation.
Autre
Si le thread est bloqué sur une ressource, par exemple si votre tâche est en train d’écouter sur un socket, invoquer interrupt() (ou même stop() ou même un sémaphore fait maison) n'aura aucun effet. Vous devrez donc fermer cette ressource (ici le socket) depuis un autre thread pour induire un état d'erreur dans votre code à l'endroit où cette ressource est manipulée.
Pour obtenir une référence sur le thread courant, c'est-à-dire celui qui est en train d’exécuter votre code, vous pouvez invoquer la méthode statique currentThread() de la classe java.lang.Thread. Certaines méthodes statiques de la classe Thread font d'ailleurs appel à cette méthode pour manipuler le thread courant.
Code Java : | Sélectionner tout |
Thread courant = Thread.currentThread();
On peut simuler une pause dans l'exécution d'une application en utilisant la méthode statique sleep() de la classe java.lang.Thread. Cette méthode force le thread courant à cesser son exécution pendant le temps passé en paramètre.
Par exemple :
Code Java : | Sélectionner tout |
1 2 3 4 5 | long milliSecondes = 500L; int nanosSecondes = 6000; Thread.sleep(milliSecondes, nanosSecondes); // Ou : Thread.sleep(milliSecondes); |
Ces deux méthodes sont susceptibles de générer une exception de type java.lang.InterruptedException.
Il arrive souvent de placer des boucles infinies dans le bloc de code qui doit être exécuté par un thread. Or, si ce code ne ménage pas un temps de pause dans la boucle, il peut accaparer à lui tout seul tout le temps CPU de la machine au détriment des autres threads de votre application, et même parfois au détriment des autres programmes qui sont en train de tourner sur votre machine. C'est pourquoi il est fortement recommandé de spécifier un temps de pause dans la boucle.
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Override public void run() { try { // Boucle infinie. while (true) { [...] // On met brièvement la boucle en pause. Thread.sleep(100); } } catch (InterruptedException ex) { // Gestion de l'erreur. [...] } } |
Les applications qui gèrent des accès à des ressources bloquantes peuvent se reposer sur ces appels bloquants pour ménager des temps de pause. Par exemple, un serveur peut se reposer sur l’écoute d'un socket pour mettre son application en pause ; la méthode accept() bloquera tant qu'un client ne se connecte pas au serveur, ce qui met effectivement la boucle infinie en pause :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Override public void run() { try (ServerSocket serverSocket = new ServerSocket(port)) { // Boucle infinie. while (true) { // Cet appel bloque jusqu'à ce qu'un client se connecte. Socket clientSocket = serverSocket.accept(); [...] } } catch (IOException ex) { // Gestion de l'erreur. [...] } } |
Les thread ayant la priorité la plus haute sont exécutés de préférence avant ceux disposant de la priorité la plus basse. Par défaut, un thread hérite de la priorité du thread dans lequel il est créé, ce qui peut être gênant quand on crée, par exemple, une tâche de fond pour une application graphique et qu'on désire que cette dernière n'ait pas trop d'impact sur les performances du programme. Il est possible de changer cette priorité d’exécution en invoquant la méthode setPriority() du nouveau thread. La valeur passée en argument doit être contenue entre les valeurs Thread.MIN_PRIORITY et Thread.MAX_PRIORITY.
Code Java : | Sélectionner tout |
1 2 3 4 5 | // La nouvelle tâche a la même priorité que le thread courant. Thread tache = [...] // Nous descendons la priorité de cette tâche au minium. tache.setPriority(Thread.MIN_PRIORITY); tache.start(); |
Note : le thread principal d'un programme est démarré avec la priorité Thread.NORM_PRIORITY.
Généralement un programme simple (ex. : en ligne de commande) dispose d'un seul thread et donc la JVM quitte immédiatement l'application lorsqu'elle atteint la dernière instruction de la méthode main(). Mais dans un environnement multi-thread, lorsque votre méthode main() se termine, la JVM peut ne pas quitter l'application si des threads « "normaux » ou « utilisateur » sont toujours en cours d’exécution. En effet, la JVM quittera l'application uniquement si tous les threads « normaux » sont terminés.
Ce phénomène se produit assez régulièrement dans environnement graphique Swing quand le programmeur oublie de configurer son JFrame pour indiquer que le programme doit se terminer lors de la fermeture de la fenêtre principale de l'application. En effet, le thread graphique et évènementiel EDT (Event Dispatch Thread) continue de tourner en tâche de fond, ce qui empêche la JVM de quitter l'application.
Les threads « daemon », quant à eux, n’empêchent pas la fermeture de l'application.
Par défaut, tous les threads que vous créez sont des threads « normaux » ou « utilisateur », c'est-à-dire qui peuvent bloquer la fermeture de la JVM tant qu'ils s’exécutent. Cependant, vous pouvez les configurer en « daemon » (ou « démon » ou « service ») et dans ce cas, ils ne bloqueront plus la fermeture de la JVM durant leur exécution. Ce genre de thread est souvent utilisé pour gérer des tâches de fond de basse priorité qui tournent en parallèle au thread principal de votre application tant que cette dernière existe. Vous pouvez transformer un thread normal en thread daemon en invoquant sa méthode setDaemon() et en lui passant la valeur true. Cette méthode doit être invoquée avant le démarrage de la tâche.
Code Java : | Sélectionner tout |
1 2 3 | Thread tache = [...] tache.setDaemon(true); tache.start(); |
Le double-check locking est un idiome de programmation censé assurer la sécurité du patron Singleton en environnement multi-thread. Attention, ce pattern ne marche pas !
Il peut être écrit comme suit :
Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | public static Singleton getInstance() { if (instance==null) { synchronized (Singleton.class) { if(instance==null) { instance=new Singleton(); } } } return instance; } |
Bien qu'encore recommandée sur le web, son utilisation est fortement déconseillée !
Il existe plusieurs solutions pour sécuriser un patron Singleton dans un programme multi-thread :
- synchroniser toute la méthode getInstance() et payer le coût de la synchronisation à chaque appel ;
- utiliser ThreadLocal dont l'implémentation n'est pas plus performante que la synchronisation ;
- abandonner la synchronisation et utiliser un initialisateur statique quand la construction du Singleton ne nécessite pas de paramètres particuliers ;
- abandonner la synchronisation et utiliser un enum quand la construction du Singleton ne nécessite pas de paramètre particulier.
- Tutoriel : Le Singleton en environnement Multithread par Christophe Jollivet ;
- Tutoriel : Patterns d'implémentation avec les enums Java par Laurent Claisse.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.