En Java, il y a deux façon de sortir d'un appel de méthode :
par un return
(explicite ou implicite), ou par la levée d'une exception.
Il existe deux sortes d'exceptions :
NullPointerException
, ArrayIndexOutOfBoundsException
, ... IOException
, InterruptedException
, ... et pour lesquelles on doit "agir".En Java, il n'est pas possible de forcer un thread à s'arrêter (autrement dit, de le tuer).
La seule façon d'arrêter un thread est de lui demander... gentiment, en lui signalant que l'on souhaiterait qu'il s'arrête.
Un thread peut envoyer un signal d'interruption à un autre thread :
autreThread.interrupt()
.
L'autre thread peut recevoir le signal de 2 façon différentes :
Thread.interrupted()
. Cette méthode est vraiment mal nommée et devrait s'appeler interruptedAndClear()
, car elle repositionne également le statut d'interruption à false
après son appel !var thread = Thread.ofPlatform().start(() -> { for (var l = 0; l < 1_000_000_000L; l++) { // ici, on fait un calcul plus ou moins lent } }); ... thread.interrupt();
Dans ce cas, le programme ne sait pas qu'il a été interrompu...
Il faut tester le statut d'interruption !
var thread = Thread.ofPlatform().start(() -> { for (var l = 0; l < 1_000_000_000L && !Thread.interrupted(); l++) { // ici, on fait un calcul plus ou moins lent } }); ... thread.interrupt();
Un appel de méthode est bloquant s'il met le thread courant en attente.
Par exemple :
Thread.sleep()
,wait()
.Ces méthodes devraient déclarer une (IO)InterruptedException
... Mais il faut vérifier la doc. Par exemple, les méthodes de Scanner
ne le font pas.
Un thread peut envoyer un signal d'interruption à un autre thread :
autreThread.interrupt()
.
L'autre thread peut recevoir le signal de 2 façon différentes :
Thread.interrupted()
.
InteruptedException
est levée (et le statut est positionné à false
).
L'appel à sleep
peut provoquer une InterruptedException
qui est une exception checked, il faut dont obligatoirement la gérer. Et comme on est dans une lambda, on ne peut pas faire de throws
.
var thread = Thread.ofPlatform().start(() -> { for(;;){ try { Thread.sleep(5_000); // méthode bloquante } catch (InterruptedException e) { // ici, il faut IMPÉRATIVEMENT traiter l'exception } } }); ... thread.interrupt();
Attention, un catch
qui ne fait rien, cela veut dire que l'on ne pourra jamais arrêter le thread. Donc ce code est mauvais !
Il y a deux façon de traiter cette InterruptedException
:
main
ou de runnable.run()
avec un return
; run()
du Runnable
.var thread = Thread.ofPlatform().start(() -> { for(;;){ try { Thread.sleep(5_000); // méthode bloquante } catch (InterruptedException e) { return; // ou lever une exception runtime... } } }); ... thread.interrupt();
Avec un appel bloquant suivi d'un calcul lent :
var thread = Thread.ofPlatform().start(() -> { var sum = 0; while (!Thread.interrupted()) { try { Thread.sleep(5_000); // méthode bloquante } catch (InterruptedException e) { return; } sum += findPrime(); // calcul sans appel bloquant mais lent } System.out.println(sum); }); ... thread.interrupt();
Selon le moment où l'interruption est reçue, le comportement n'est pas le même...
var thread = Thread.ofPlatform().start(() -> { var sum = 0; while (!Thread.interrupted()) { try { Thread.sleep(5_000); // méthode bloquante } catch (InterruptedException e) { Thread.currentThread().interrupt(); continue; } sum += findPrime(); // calcul sans appel bloquant mais lent } System.out.println(sum); }); ... thread.interrupt();
Ré-interrompre le thread dans le catch
permet de faire en sorte qu'on ne puisse sortir de la boucle que d'une seule façon.
Remarque : ici, un break
aurait eu le même effet.
var thread = Thread.ofPlatform().start(() -> { var sum = 0; while (!Thread.interrupted()) { sum += findPrime(); // calcul sans appel bloquant mais lent try { Thread.sleep(5_000); // méthode bloquante } catch (InterruptedException e) { break; } } System.out.println(sum); }); ... thread.interrupt();
Si l'interruption survient lors du calcul de findPrime
, le prochain appel bloquant à sleep()
lève immédiatement une InterruptedException
Une partie des opérations d’entrée/sortie sont des appels bloquants : Reader.read()
, OutputStream.write()
, ...
IOException
;InterruptedException
;IOInterruptedException
(sous-classe de IOException
);Donc, il ne faut pas oublier de les traiter, et lorsqu'on reçoit une IOInterruptedException
, il faut tout fermer et arrêter le thread (ou pour un serveur, arrêter la connexion avec le client, etc..)
synchronized
Essayer d'entrer dans un bloc synchronized
est une opération bloquante si un autre thread a déjà pris le jeton associé au lock de ce bloc. Attention, elle ne lève pas d'InterruptedException...
S'il y a un risque d'attente (en cas de forte contention, par exemple) et que l'on veut permettre l'interruption de l'entrée dans une section critique, il faut utiliser des ReentrantLock
et la méthode lockInterruptibly()
.
Habituellement, appeler interrupt()
sur un thread veut dire qu'on lui demande de s'arrêter.
Mais ce n'est qu'une convention, le code du thread peut faire ce que bon lui semble (pourvu que cela soit écrit dans la javadoc).
Donc l’interruption d'un thread se fait de façon coopérative : le code du thread doit être écrit pour qu'il puisse s'interrompre (et ne rien faire, ou appeler printStackTrace
n'est jamais la bonne solution !).
On distingue deux cas :
InterruptedException.
Thread.interrupted()
.