juin
2009
Gérer proprement les interruptions de threads en Java
Voici la traduction rapide d'un article d'Alex Miller (avec son aimable permission naturellement), qui récapitule les grands principes de gestion de l'interruption des threads.
Un sujet souvent mal maîtrisé, qui pourtant, vous allez le voir, est relativement simple à comprendre.
Gestion des interruptions, par Alex Miller
Un thread Java ne peut pas être interrompu de manière préemptive. En revanche, une application peut appeler sa méthode Thread.interrupt()
. Si le thread est en attente d'une opération bloquante à ce moment-là (comme wait
, join
ou sleep
), une InterruptedException
est levée. Dans le cas contraire, seul le flag interrupted
du thread est activé.
Un thread recevant une InterruptedException
a plusieurs options :
- Gérer lui-même l'interruption. Le traitement typique consiste à vérifier si une condition particulière a été atteinte (si le thread doit s'arrêter ou changer d'état), et à modifier le comportement du thread en conséquence.
- Propager l'exception. Celle-ci est immédiatement relancée, et sa gestion incombe alors à du code de plus haut niveau.
- Retarder la gestion de l'exception. Il suffit pour cela d'appeler
Thread.currentThread().interrupt()
de manière à réactiver le flag d'interruption du thread. Du code de plus haut niveau peut alors vérifier l'état de ce flag et prendre les mesures adéquates. - Ignorer l'exception. L'exception est capturée et étouffée silencieusement. Cette solution est à proscrire - à moins naturellement que le bloc
catch
ne contienne tout le code nécessaire au traitement de l'interruption,auquel cas il ne s'agit que d'un cas particulier de la première option.
Réfléchissez soigneusement à la politique de gestion des interruptions de vos threads, et à leur réaction dans le cas où ils sont interrompus en pleine attente d'une opération bloquante. De manière générale, les frameworks techniques devraient propager les exceptions de manière transparente, et laisser les applications les gérer en accord avec la politique d'interruption de leurs threads.
Notes personnelles
Voici un exemple de code gérant correctement l'interruption.
Le thread lancé effectue des boucles d'une seconde à l'aide de la méthode bloquante sleep(). L'utilisateur peut déclencher l'interruption de ce thread en saisissant quelquechose dans la console, et constater que la boucle s'est terminée prématurément.
public class ThreadInterruption { public static void main(String[] args) throws Exception { // Démarrage du thread CounterThread counter = new CounterThread(); counter.start(); // Attente d'une interruption manuelle System.in.read(); System.out.println("Interruption !"); counter.interrupt(); // Attente de la fin du thread counter.join(); } public static class CounterThread extends Thread { @Override public void run() { // Tant que le thread n'est pas interrompu... long time = 0L; while (! isInterrupted()) { time = System.currentTimeMillis(); try { // Une seconde d'attente Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("J'ai été interrompu !"); // Activation du flag d'interruption Thread.currentThread().interrupt(); } System.out.println("Boucle de " + (System.currentTimeMillis() - time) + "ms"); } } } }
Résultat :
Début de la boucle, attente prévue 1000ms Fin de la boucle, attente réelle 1000ms Début de la boucle, attente prévue 1000ms Fin de la boucle, attente réelle 1000ms Début de la boucle, attente prévue 1000ms Fin de la boucle, attente réelle 1000ms Début de la boucle, attente prévue 1000ms <saisie dans la console> Interruption ! J'ai été interrompu ! Fin de la boucle, attente réelle 479ms
Commentaires
Il n'y aurait pas une erreur ligne 37:
Thread.currentThread().interrupted();
et non:
Thread.currentThread().interrupt();
Non, c'est bien interrupt(), voici pourquoi :
Si le thread est interrompu alors qu'il est dans l'état WAITING (pendant sleep()), il est automatiquement remis en mode RUNNABLE et la première action qu'il entreprendra sera de lancer la fameuse InterruptedException.
Le problème, c'est que le fait de capturer l'InterruptedException remet à zéro l'indicateur d'interruption (un flag interne du thread).
Or, on veut que ce flag soit actif pour pouvoir sortir de la boucle "while (! isInterrupted())". Et la méthode interrupt() a justement cet effet.
Mais alors, que fait la méthode interrupted() ?
La méthode interrupted() signale au thread que l'on a bien vu qu'il était interrompu, et rabaisse le flag d'interruption. Ce n'est pas le comportement qui nous intéresse ici.
Oui effectivement, merci pour ta réponse et au passage bravo pour la qualité de tes billets !
Bonjour,
la méthode run de mon thread vérifie une file d'attente en base de données, et lance un traitement )qui peut prendre 30 min, 1h, ou même 3h) sur le premier objet de la file d'attente si celle-ci n'est pas vide.
Ce traitement est représenté dans la méthode run par un appel à monObjetDeTraitement.traite(monObjetATraiter);
lorsque j'appelle la méthode interrupt, mon traitement n'est pas arrêté sur le champ, si je suis au milieu d'une boucle qui prend 5 min elle va continuer et le traitement ne s'arrêtera que si j'appelle une méthode d'une classe qui renvoie une ClosedByInterruptException. Dois-je truffer mon code de tests pour savoir si je dois ou non arrêter mon traitement ?
Merci,