Est Fil.interrupt() mal?
Un coéquipier a fait la réclamation suivante:
"
Thread.interrupt()
est intrinsèquement cassé, et ne devrait (presque) jamais être utilisé".
J'essaie de comprendre pourquoi c'est le cas.
Est-ce une bonne pratique connue de ne jamais utiliser Thread.interrupt()
? Pouvez-vous fournir des preuves pourquoi il est cassé / bogué , et ne devrait pas être utilisé pour écrire du code multithread robuste?
Note - Je ne suis pas intéressé par cette question si elle est" jolie " d'un conservateur de conception. Ma question Est-est il buggé?
7 réponses
Version Courte:
Est-ce une bonne pratique connue de ne jamais l'utilisation de la Thread.interrupt()?
Non.
Pouvez-vous fournir preuve pourquoi il est cassé / buggie, et ne doit pas être utilisé pour l'écriture code multithread robuste?
Le contraire est vrai: il est essentiel pour le code multithread.
Voir Listing 7.7 dans Java Concurrency dans la pratique pour un exemple.
Plus longtemps version:
Ici, nous utilisons cette méthode dans un endroit spécifique: handling InterruptedExceptions
. Cela peut sembler un peu étrange mais voici à quoi cela ressemble dans le code:
try {
// Some code that might throw an InterruptedException.
// Using sleep as an example
Thread.sleep(10000);
} catch (InterruptedException ie) {
System.err.println("Interrupted in our long run. Stopping.");
Thread.currentThread().interrupt();
}
Cela fait deux choses pour nous:
- Il évite de manger l'exception d'interruption. Les gestionnaires D'exceptions automatiques IDE vous fournissent toujours quelque chose comme
ie.printStackTrace();
et un "todo" jaunty: quelque chose d'utile doit aller ici!" commentaire. - Il restaure l'état d'interruption sans forcer une vérification exception sur cette méthode. Si la signature de méthode que vous implémentez n'a pas de clause
throws InterruptedException
, c'est votre autre option pour propager ce statut interrompu.
Un commentateur a suggéré que je devrais utiliser une exception non cochée " pour forcer le thread à mourir."Cela suppose que j'ai une connaissance préalable que tuer le fil brusquement est la bonne chose à faire. Je ne sais pas.
Pour citer Brian Goetz de JCIP sur la page précédant la liste citée ci-dessus:
Une tâche ne doit pas assumer quoi que ce soit sur la Politique d'interruption de son exécution du thread sauf s'il est explicitement conçu pour s'exécuter dans un un service qui a une politique d'interruption.
Par exemple, imaginez que j'ai fait ceci:
} catch (InterruptedException ie) {
System.err.println("Interrupted in our long run. Stopping.");
// The following is very rude.
throw new RuntimeException("I think the thread should die immediately", ie);
}
Je déclarerais que, indépendamment des autres obligations du reste de la pile d'appels et de l'état associé, ce thread doit mourir maintenant. Je voudrais essayer de passer en douce tous les autres blocs de capture et code de nettoyage de l'État pour aller directement à la mort du fil. Pire, j'aurais consommé le statut interrompu du fil. La logique amont devrait maintenant déconstruire mon exception pour essayer de savoir s'il y avait une erreur de logique de programme ou si j'essaie de cacher une exception vérifiée dans un wrapper obscurcissant.
Par exemple, voici ce que tout le monde dans l'équipe aurait immédiatement à faire:
try {
callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
if (e.getCause() instanceOf InterruptedException) {
// Man, what is that guy's problem?
interruptCleanlyAndPreserveState();
// Restoring the interrupt status
Thread.currentThread().interrupt();
}
}
L'état interrompu est plus important que n'importe quel InterruptException
spécifique. Pour un exemple spécifique pourquoi, voir le javadoc pour Thread.interrompre():
Si ce thread est bloqué dans une invocation de wait (), wait(long), ou attendre (long, int) méthodes de la classe D'objet, ou de la jointure(), join(long), join(long, int), sleep(long) ou sleep(long, int), les méthodes de cette classe, alors son statut d'interruption sera effacé et il recevoir une exception InterruptedException.
Comme vous pouvez le voir, plus D'une exception InterruptedException pourrait obtenir créés et gérés en tant que demandes d'interruption sont traités mais uniquement si cet état d'interruption est préservé.
La seule façon dont je suis conscient de dans lequel Thread.interrupt()
est cassé est qu'il ne fait pas réellement ce qu'il semble qu'il pourrait - il ne peut réellement interrompre le code qui l'écoute.
Cependant, utilisé correctement, il me semble être un bon mécanisme intégré pour la gestion des tâches et l'annulation.
Je recommande Java Concurrency dans la pratique pour plus de lecture sur l'utilisation correcte et sûre de celui-ci.
Le principal problème avec Thread.interrupt()
est que la plupart des programmeurs ne connaissent pas les pièges cachés et l'utilisent de la mauvaise façon. Par exemple, lorsque vous gérez l'interruption, il existe des méthodes qui effacent le drapeau (de sorte que le statut est perdu).
En outre, l'appel n'interrompt pas toujours le thread tout de suite. Par exemple, quand il se bloque dans une routine du système, rien ne se passera. En fait, si le thread ne vérifie pas le drapeau et n'appelle jamais une méthode Java qui lance InterruptException
, alors s'interrompant, il n'aura aucun effet que ce soit.
Non, ce n'est pas bogué. C'est en fait la base de la façon dont vous arrêtez les threads en Java. Il est utilisé dans le framework Executor de java.util.concurrent-voir l'implémentation de java.util.concurrent.FutureTask.Sync.innerCancel
.
Quant à l'échec, Je ne l'ai jamais vu échouer, et je l'ai largement utilisé.
Une raison non mentionnée est que le signal d'interruption peut être perdu, ce qui rend l'appel du Thread.méthode d'interruption () sans signification. Donc, à moins que votre code dans le fil.la méthode run() tourne dans une boucle while le résultat de L'appel du Thread.interrompre () est incertain.
J'ai remarqué que dans thread ONE
j'exécute DriverManager.getConnection()
quand il n'y a pas de connexion à la base de données disponible (disons que le serveur est en panne donc finalement cette ligne jette SQLException
) et à partir du thread TWO
j'appelle explicitement ONE.interrupt()
, alors ONE.interrupted()
et ONE.isInterrupted()
retournent false
même si placé comme première ligne dans le bloc catch{}
où SQLException
est géré.
Bien sûr, j'ai travaillé sur ce problème en implémentant le sémaphore supplémentaire, mais c'est assez gênant, car c'est le tout premier problème de ce type dans Mes années 15 Java développement.
Je suppose que c'est à cause d'un bug dans com.microsoft.sqlserver.jdbc.SQLServerDriver
. Et j'ai étudié plus pour confirmer que l'appel à la fonction native
consomme cette exception dans tous les cas, elle montre la sienne, mais la conserve lorsqu'elle est réussie.
Tomek
P.S. j'ai trouvé le problème analogue .
P. P. S
Je joins un très court exemple de ce que j'écris ci-dessus. La classe enregistrée peut être trouvée dans sqljdbc42.jar
. J'ai trouvé ce bug dans les classes construites sur 2015-08-20 puis j'ai mis à jour à la nouvelle version disponible (à partir de 2017-01-12) et le bug existe toujours.
import java.sql.*;
public class TEST implements Runnable{
static{
try{
//register the proper driver
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
}
catch(ClassNotFoundException e){
System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
}
}
public void run() {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().isInterrupted());
//prints true
try{
Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
}
catch (SQLException e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().isInterrupted());
//prints false
System.exit(0);
}
public static void main(String[] args){
(new Thread(new TEST())).start();
}
}
Si vous passez quelque chose de complètement incorrect, comme "foo"
, au DriverManager.getConnection()
, vous obtiendrez le message "Aucun pilote approprié trouvé pour foo", et la deuxième impression sera toujours vraie comme on pourrait s'y attendre. Mais si vous passez la chaîne correctement construite mais, disons, que votre serveur est en panne ou que vous avez perdu votre connexion réseau (cela peut généralement se produire dans l'environnement de production), vous verrez l'erreur java.net
socket timeout l'impression et L'état interrupted()
du thread sont perdus.
Le problème n'est pas que l'implémentation n'est pas boguée mais plutôt que votre thread est dans un état inconnu lorsqu'il est interrompu et cela peut conduire à un comportement imprévisible.