Threads en JAVA
Comme je l'ai déjà écris par ailleurs (pour Windows en introduction ou Unix), le thread est la plus petite unité d'exécution. Le temps d'exécution est découpé en tranches ou quantum ou quotas (time slice). Lorsqu'un thread en exécution a épuisé son slot de temps, qu'il est fini ou non ses traitements en cours, il sera remis (préempté) dans une queue d'élection dans l'attente d'être choisi à nouveau par le sheduler (répartiteur ou gestionnaire de tâches). C'est le multi tâche préemptif.
Tous codes qui s'exécutent le sera dans un thread géré par le système. Mais ces codes peuvent eux aussi lancer des threads.
Etat d'un thread
- Un thread est dans l'état nouveau lors de sa création.
- Il passe à l'état exécuté lors de l'appel à une méthode start.
- Il est exécutable lorsqu'il est dans la queue d'élection en attendant d'être (éventuellement de nouveau) choisi par le scheduler.
- Il est en cours d'exécution lorsqu'il a été choisi par le scheduler et durant tout son quotas de temps. Lorsque se quotas est atteint, il repasse en exécutable et retourne dans la queue d'élection.
- Il est dans un état bloqué si celui-ci demande à passer en sommeil et le restera durant tout le temps de son sommeil. Il repassera ensuite en exécutable.
- Il meurt lorsqu'il a terminé d'exécuter la dernière instruction du thread.
Un thread pourra avoir
un niveau de priorité afin d'être choisi plus ou moins rapidement
par le scheduler.
Comme pour tout ce genre de priorité, il ne faut pas en abuser !
Récupérer des informations du thread courant
Pour prouver ce que j'ecris ci-dessus, vous pouvez créer une classe avec la méthode main et demander des informations sur le thread courant.
Pour récupérer des informations sur le thread courant, nous utiliserons la méthode Thread.currentThread():
Par exemple:
System.out.println("TstThread" + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
Principe de fonctionnement
Il existe deux solutions pour se créer un thread, soit en créant une sous classe de Thread, soit en implémentant l'interface Runnable.
Les deux solutions fonctionnent sur le même principe:
- La méthode start() permet de démarrer un thread.
- La méthode run() contient le code qui sera exécuté par ce thread.
- Une méthode sleep() permet de mettre en sommeil un thread.
- Une méthode interrupt() pour interrompre un thread.
Des informations sur le thread peuvent être récupérées:
- isAlive pour savoir si le thread vit encore (true) ou est mort (false).
- isInterrupted pour savoir si le thread a été interrompu ou non.
Il existe aussi des getters pour récupérer différentes informations sur le thread, des setters, ...
Il est tout à
fait possible de lancer la méthode run(), mais ce n'est pas bien normal
! En effet, votre code sera bien exécuté, cependant, cela ne serait
pas dans un thread séparé, mais dans votre thread courant. Il
faut donc bien appeler la méthode start() pour créer un nouveau
thread !
En passant par la classe Thread
En créant une sous-classe de Thread, vous coderez votre traitement en surchargeant la méthode run().
Pour lancer ce thread, il suffira d'instancier votre sous-classe puis de lancer la méthode start de votre instance.
Une sous classe de Thread
Pour tester le thread:
En fait, la classe
Thread implémente l'interface Runnable.
En implémentant Runnable
Il est possible de créer une classe qui implémente l'interface Runnable. Mais il faudra tout de même passer par la classe Thread pour lancer votre Thread.
Rappel, l'implémentation ici sera fort utile dans le cas où la classe que vous voulez exécuter dans un thread est déjà une sous classe de quelque chose (extends classe déjà utilisé dans la définition de votre classe). En effet, JAVA interdit le multi-héritages.
L'interface Runnable contient (uniquement) la méthode void run().
Voici un exemple de classe implémentant Runnable
Et le code pour tester tout cela
Faire une pause dans son traitement courant
Notre classe courante s'exécutant elle même dans un thread, il est tout à fait possible d'utiliser sleep() pour faire une boucle d'attente. C'est d'ailleurs nettement préférerable comme solution à une boucle for () ou while() pour faire une attente. Car vous ne connaissez pas le temps que prendra réellement ce dernier type de boucle qui dépendra fortement de la machine l'exécutant !
Interrompre un thread
Il est déconseillé d'arrêter un thread en cours d'exécution, il est préférable d'attendre qu'il se termine de lui même. D'ailleurs certaines méthodes qui permettaient cela sont maintenant marquées comme obsolètes.
La solution qui reste est de tester dans le thread la valeur retournée par isInterrupted(). Il vous sera donc possible de postionner le flag interruption par la méthode interrupt() sur le thread pour l'en informer, mais sera sans effet sur le thread lui même si le thread ne contrôle pas ce flag durant son traitement.
Interrompre une pause dans un thread
Lorsque vous envoyez sur un thread en sommeil (méthode sleep()) une interruption (méthode interrupt()), alors le thread est "réveillé" et InterruptedException est levé.
Cependant, le thread courant n'est pas positionné comme étant interrompu, c'est uniquement le sleep qui est interrompu. De plus, le thread va continuer normalement son traitement juste après le catch.
Si vous ne voulez pas que votre thread réagisse ainsi, mais qu'il arrête bien tous ses traitements:
- Il sera fortement conseillé de positionner le flag d'interruption sur le thread afin que les threads qui en auraient besoin soient informés que le thread en question ne s'est pas terminé normalement. Vous utiliserez pour cela:
Thread.currentThread().interrupt() dans le thread dont la mise en sommeil a été interrompue afin de positionner le flag interruption.
- Il est impératif dans le catch de faire un return pour sortir du thread.
D'où le nouveau code pour la sous classe de thread
Et le code pour l'interrompre
Priorité d'un thread
Il sera possible de définir une priorité pour un thread. Vous utiliserez pour cela la méthode setPriority
Synchronisation
Des conflits d'accès à des données peuvent apparaître si plusieurs threads peuvent accéder à un même objet pour lire et modifier en même temps ces paramètres.
Pour éviter cela, vous utiliserez le mot clé synchronized. Ce mot clef permet en effet de vérrouiller de manière exclusive un objet. Les autres threads qui voudraient ainsi vérouiller eux aussi l'objet ne le pourront que lorsque celui-ci sera déverrouillé par le thread qui l'aura précédemment vérouillé.
Tout objet java possède
un verrou permettant ainsi l'accès simultané à cet objet
si ce verrou est utilisé.
Deux solutions sont possibles pour utiliser ce mot clé:
- Soit en vérrouillant un objet, ainsi le code sera synchronisé sur cet objet vérrouillé
Soit sur une méthode de la classe courante
Avec type la valeur que pourra retrouner la méthode en sortie
Si le thread ne peut pas se synchroniser sur l'objet car vérrouillé par un autre thread, alors celui-ci passe dans un état bloqué et ne repassera dans un état exécutable que lorsque le verrou sera levé. Il pourra alors de nouveau tenter de vérouiller cet objet.
Attendre la fin d'un thread
Lorsque deux threads tournent, par exemple votre thread courant et un thread t1, à un moment de votre traitement dans le thread courant, il faudra peut-être attendre la fin du traitement du thread t1 avant de continuer celui du thread courant.
Il existe une méthode: join() qui permet d'attendre la fin d'un thread.
La méthode join pourra éventuellement recevoir en paramètre une durée en ms ou en ms et nanos. Cette durée étant le délai maximum d'attente avant de considérer qu'il y a un time out. Dans ce cas, il faudra peut-être tester isAlive sur le thread t1 avant de continuer, voire le tuer ?
Voici un exemple, qui reprend SubThread (vu ci-dessus)
Synchroniser des traitements dans un ordre déterminé
Le problème vient ici du fait que les threads ne sont pas censés se connaître les uns les autres !
wait, notify et notifyall
A venir !