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, ... 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 avec la méthode d'instance
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.
Lorsque un thread envoie un signal d'interruption à un autre thread avec la méthode d'instance
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..)
synchronizedEssayer 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().