Bien fermer ses threads en Java

Formation

En Semi-présenciel Paris

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En semi-présentiel

  • Lieu

    Paris

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Les sites et dates disponibles

Lieu

Date de début

Paris ((75) Paris)
Voir plan
7 Cité Paradis, 75010

Date de début

Consulter

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Avant de commencer, je considère que si vous lisez ce tutoriel, vous avez déjà des bases en Java et que vous savez ce qu'est un thread. Si tel n'est pas le cas, vous pouvez toujours aller lire le tutoriel de cysboy, en particulier la partie sur les threads.

Sur ce, bonjour à tous.

Dans cet article, nous allons traiter de la fermeture des threads. Si vous avez déjà programmé avec des threads, vous avez probablement été confrontés au problème de leur arrêt.

Certains, peu scrupuleux, auront tout simplement utilisé la méthode Thread.stop (pourtant dépréciée), tandis que d'autres auront fait des recherches pour essayer de les fermer de la façon la plus propre possible.

Nous allons donc voir pourquoi il ne faut pas utiliser cette fameuse méthode Thread.stop , et ce qu'il faut faire à la place.

Mais pourquoi la méthode Thread.stop est-elle dépréciée ?

Tout d'abord, il faut déjà comprendre ce que veut dire déprécié. Si nous passons rapidement par le dictionnaire du CNRTL, nous trouvons la définition suivante :

Citation : CNRTL

  • faire baisser la valeur de (quelque chose), diminuer sa valeur ;

  • rabaisser la valeur de (quelque chose), porter des jugements défavorables sur ;

  • Rabaisser (la valeur ou le mérite de) ; porter ou exprimer un jugement défavorable sur.

Ce que nous retiendrons pour notre usage informatique, est qu'une méthode dépréciée est à éviter. Si elle est dépréciée, c'est qu'il existe une alternative pour faire la même chose ou mieux.

Maintenant, passons aux raisons de la dépréciation. Ce n'est pas parce que la méthode est moins optimisée qu'une autre, mais bien parce qu'elle est dangereuse d'utilisation et induit des comportements aléatoires.

Pour expliciter cela, regardons rapidement la documentation :

Citation : Javadoc

Deprecated. This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait. For more information, seeWhy are Thread.stop, Thread.suspend and Thread.resume Deprecated?.

En résumé, cette méthode n'est pas sûre. L'utiliser pour arrêter un thread provoque le déverrouillage de toutes les ressources utilisées par le dit thread. Si une de ces ressources n'était pas dans un état stable, elle va être rendue accessible dans son état instable, ayant pour effet un comportement incontrôlable et aléatoire lors de l'usage de la ressource par d'autres threads. La documentation explique ensuite une méthode simple et générique d'arrêt « propre » de vos threads, et vous renvoie vers un lien si vous voulez en savoir plus sur le pourquoi du comment.

Pour mieux comprendre le problème, rien ne vaut un exemple.

Imaginons deux threads qui accèdent à un objet « Personne », l'un pour écrire, l'autre pour lire.
Le code est bien fait, les deux threads ne peuvent accéder à l'objet en même temps. Maintenant, imaginons que l'objet « Personne » réponde à un instant « t » aux valeurs suivantes : {Prénom = Sophie, Nom = Dupond, âge = 23 ans, taille = 170 cm, salaire = 32000 ? / an}. Le premier thread va pour le modifier en {Jean, Durant, 2 ans, 65 cm, 0 ? / an}. Il commence sa besogne, mais au milieu de son travail, il se fait arrêter (par Thread.stop ). Il se ferme et le verrou se libère. Or, il se trouve que le deuxième thread était justement en attente de la libération de ce verrou pour lire l'objet. Il va donc pour effectuer sa lecture, mais lit {Jean, Durant, 3 ans, 170 cm, 32000 ? / an} parce que le premier thread n'a pas eu le temps de finir son écriture avant d'être fermé. Les valeurs que le deuxième thread récupère sont erronées et surtout inexploitables ! Elles ne correspondent à personne. Il va donc travailler avec de « mauvaises » valeurs, produisant bien entendu un résultat incohérent.

On ne peut prédire ce que le premier thread aura modifié dans l'objet lors de son arrêt. C'est en ce sens que le comportement du reste du programme va devenir aléatoire. On n'a aucune idée de l'état dans lequel seront les données.

Je vous ai fait un exemple avec un objet « Personne » pour faire simple, mais vous pouvez remplacer cet objet par n'importe quelle autre action ou ressource ; vous comprendrez alors que les conséquences d'un code approximatif, dont on ne contrôle pas parfaitement la portée, peuvent être désastreuses.

Une alternative, la boucle conditionnelle

Maintenant que nous savons comment il ne faut pas faire, voyons comment il faut faire.
Cette première solution que je vais vous présenter suffit dans une grande partie des cas. Elle est simple à mettre en œuvre et ne nécessite que très peu de code.

Prenons pour l'exemple un thread standard codé pour être arrêté avec Thread.stop :

class MonThread extends Thread { public Monthread () { // Mon constructeur } @Override public void run() { // Initialisation while(true) { // Boucle infinie pour effectuer des traitements. // Traitement à faire } } }

Il suffit juste de changer et de mettre une condition dans la boucle while(running) avec running un booléen, et le tour est joué.

Pour changer la valeur de running, deux possibilités :

  • mettre une condition dans la boucle infinie ;

  • écrire une méthode.

Nous obtenons finalement le code suivant :

class MonThread extends Thread { protected volatile boolean running = true; public Monthread () { // Mon constructeur } public arret() { // Méthode 2 running = false; } @Override public void run() { // Initialisation while(running) { // Boucle infinie pour effectuer des traitements. // Traitement à faire if (/*Condition d'arret*/) // Méthode 1 running = false; } } } Pour un code bloquant, l'utilisation des interruptions

Comme on peut vite s'en rendre compte, l'utilisation du code précédent ne fonctionne que si le thread boucle régulièrement (puisqu'il ne remarquera la demande d'arrêt qu'au commencement de la boucle). Imaginons maintenant que le thread fasse de longues attentes (comme un Thread.sleep de 5 minutes, un wait , un Thread.join ou autre), il faudrait alors attendre que l'attente de ce dernier se termine avant qu'il ne se ferme. Cette option n'est bien entendu pas envisageable, c'est pourquoi il existe un mécanisme d'interruption des threads lorsqu'ils sont en attente. Ce mécanisme répond à l'appel de la méthode Thread.interrupt.

Passons à la pratique, car son utilisation est quelque peu délicate. Prenons pour exemple le code suivant :

public class MonThread extends Thread { @Override public void run() { while(true) { // Traitement try { Thread.sleep(100000); // Pause de 100 secondes } catch (InterruptedException ex) { Thread.currentThread().interrupt(); // Très important de réinterrompre break; // Sortie de la boucle infinie } } } public static void main(String[] args) throws InterruptedException { Thread t = new MonThread(); t.start(); Thread.sleep(1000); // Attente 1 seconde avant d'interrompre t.interrupt(); } }

Lorsque le main appelle t.interrupt();, cela interrompt l'attente du thread « t » et lève l'exception InterruptedException. Il faut donc faire un catch de cette exception et la traiter comme la condition de sortie de boucle (c'est pour ça que l'on fait un break;).

Même si le thread n'est pas en attente sur le code bloquant au moment de l'interruption, celui-ci va continuer jusqu'à y arriver. Lorsqu'il arrivera au code bloquant, il lèvera alors l'exception.

Le seul morceau de code qu'il reste à expliquer est maintenant la ligne Thread.currentThread().interrupt();.

Mais pourquoi faut-il que le thread s'interrompe lui-même ?

Encore une fois, prenons un exemple :

public class MonThread extends Thread { private static boolean correct = true; @Override public void run() { while (true) { // Traitement 1 for (int i = 0; i < 10; i++) { // Boucle imbriquée // Traitement 2 try { Thread.sleep(100); } catch (InterruptedException ex) { if (correct) Thread.currentThread().interrupt(); // réinterruption sur soi-même System.out.println("premier Catch"); break; // Sortir de la boucle <italique>for</italique> } } try { // Traitement 3 System.out.print("Devant le sleep, "); Thread.sleep(1000); System.out.print("Derrière le sleep, "); } catch (InterruptedException ex) { if (correct) Thread.currentThread().interrupt(); System.out.println("Deuxième catch"); break; // Sortir de la boucle <italique>while</italique> } } System.out.println("Fermeture du Thread"); } private static void test() throws InterruptedException { Thread t = new MonThread(); t.start(); Thread.sleep(2500); t.interrupt(); t.join(); // Attente de la fermeture du <italique>thread</italique> avant de continuer. System.out.println("Fin du Thread"); // Le <italique>thread</italique> a été fermé. } public static void main(String[] args) throws InterruptedException { test(); correct = false; // Désactive la réinterruption du <italique>thread</italique> sur lui-même. test(); } }

Si vous exécutez ce code, vous verrez qu'il ne s'arrête jamais (il ne sort pas de la boucle infinie), et on obtient la sortie suivante :

Citation : Résultat

Devant le sleep, Derrière le sleep, Premier catch
Devant le sleep, Deuxième catch
Fermeture du Thread
Fin du Thread
Devant le sleep, Derrière le sleep, Premier catch
Devant le sleep, Derrière le sleep, Devant le sleep, Derrière le sleep, Devant le sleep, Derrière le sleep, ...

On constate que si l'on relance l'interruption, celle-ci est détectée par le sleep de la boucle infinie englobante, qui donc passe dans le catch pour fermer le thread proprement. En revanche, si l'interruption n'est pas relancée, elle n'atteint pas le sleep la boucle englobante qui donc continue son exécution normalement.

On notera bien que lors de la relance de l'interruption, le « Traitement 3 » est tout de même exécuté. Ce n'est que lors du Thread.sleep que l'exception est catchée. Ce « Traitement 3 » permet justement d'éventuel changement pour l'intégrité des données avant la fermeture définitive du thread. Si vous ne voulez pas passer par ce traitement, un simple return; suffit.

L'exception des InputStream

Bien sûr, tout ne pouvait être si beau et si simple, il fallait que ça ne fonctionne pas quelque part. C'est sur les lectures de flux que nous allons avoir un problème. Si votre thread est bloqué sur une ES, l'interruption ne fonctionnera pas. Java a prévu, pour pallier ce problème, une autre exception : InterruptedIOException qui hérite de IOException . Malheureusement, cette exception n'est pas encore bien gérée et ne fonctionne pas sur toutes les ES.

Ma source sur la mauvaise gestion des InterruptedIOException date un peu, mais je n'ai pas trouvé d'informations plus récentes. Si quelqu'un a d'autres informations sur le sujet, qu'il n'hésite pas à m'envoyer un MP.

Le meilleur (seul) moyen pour sortir de cette attente devient donc la fermeture du flux bloquant (j'admets que ce n'est pas génial). Heureusement, la méthode Thread.interrupt n'est pas final , il est donc possible de surcharger la méthode pour fermer le flux dans l'interruption.

Voici un code en exemple :

import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.net.ServerSocket; import java.net.Socket; public class MonThread extends Thread { private InputStream in; public MonThread(InputStream in) { this.in = in; } @Override public void interrupt() { super.interrupt(); try { in.close(); // Fermeture du flux si l'interruption n'a pas fonctionné. } catch (IOException e) {} } @Override public void run() { try { System.out.println("Avant la lecture"); in.read(); System.out.println("Après la lecture"); } catch (InterruptedIOException e) { // Si l'interruption a été gérée correctement. Thread.currentThread().interrupt(); System.out.println("Interrompu via InterruptedIOException"); } catch (IOException e) { if (!isInterrupted()) { // Si l'exception ne vient pas d'une interruption. e.printStackTrace(); } else { // <italique>Thread</italique> interrompu mais <italique>InterruptedIOException</italique> n'était pas gérée pour ce type de flux. System.out.println("Interrompu"); } } } public static void test1() // Test avec un socket throws IOException, InterruptedException { ServerSocket ss = new ServerSocket(4444); // Pour accepter la connexion <italique>socket</italique>. Socket socket = new Socket("localhost", 4444); Thread t = new MonThread(socket.getInputStream()); t.start(); Thread.sleep(1000); t.interrupt(); t.join(); } public static void test2() // Test avec un <italique>PipedOutputStream</italique> throws IOException, InterruptedException { PipedInputStream in = new PipedInputStream(new PipedOutputStream()); Thread t = new MonThread(in); t.start(); Thread.sleep(1000); t.interrupt(); t.join(); } public static void main(String[] args) throws IOException, InterruptedException { test1(); test2(); } }...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Bien fermer ses threads en Java

Prix sur demande