Question

Un coéquipier a fait la réclamation suivante:

"Thread.interrupt() est intrinsèquement brisé et ne devrait (presque) jamais être utilisé ".

J'essaie de comprendre pourquoi c'est le cas.

Est-ce une meilleure pratique connue de ne jamais utiliser Thread.interrupt()? Pouvez-vous fournir des preuves pourquoi elle est cassée / buggy, et ne devrait pas être utilisée pour écrire du code multithread robuste?

Noter - Je ne suis pas intéressé par cette question si elle est "jolie" d'un conservateur de conception. Ma question est - est-ce buggy?

Était-ce utile?

La solution

Version courte:

Est-ce une meilleure pratique connue de ne jamais utiliser thread.interrupt ()?

Non.

Pouvez-vous fournir des preuves pourquoi elle est cassée / buggie, et ne devrait pas être utilisée pour écrire un code multithread robuste?

L'inverse est vrai: il est essentiel pour le code multithread.

Voir Listing 7.7 dans Concurrence de Java dans la pratique à titre d'exemple.

Version plus longue:

Ici, nous utilisons cette méthode à un endroit spécifique: Manipulation InterruptedExceptions. Cela peut sembler un peu étrange, mais voici à quoi il 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:

  1. Il évite de manger l'exception d'interruption. Les gestionnaires d'auto-exception des IDE vous fournissent toujours quelque chose comme ie.printStackTrace(); Et un "Todo: quelque chose d'utile doit aller ici!" commentaire.
  2. Il restaure l'état d'interruption sans forcer une exception vérifiée sur cette méthode. Si la signature de la méthode que vous implémentez n'a pas de throws InterruptedException Clause, c'est votre autre option pour propager ce statut interrompu.

Un commentateur a suggéré que je devrais utiliser une exception non contrôlée "pour forcer le fil à mourir". Cela suppose que j'ai une connaissance préalable que tuer brusquement le fil est la bonne chose à faire. Je ne sais pas.

Pour citer Brian Goetz de JCIP sur la page avant la liste citée ci-dessus:

Une tâche ne doit rien supposer sur la politique d'interruption de son fil d'exécution à moins qu'il ne soit explicitement conçu pour s'exécuter au sein d'un service qui a une politique d'interruption spécifique.

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 fil doit mourir dès maintenant. J'essaierais de me faufiler tous les autres blocs de capture et d'énoncer le code de nettoyage pour obtenir directement la mort. Pire, j'aurais consommé le statut interrompu du fil. La logique en amont devrait désormais déconstruire mon exception pour essayer de vous décoller s'il y avait une erreur de logique de programme ou si j'essaie de masquer une exception vérifiée dans un wrapper obscurci.

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 tout spécifique InterruptException. Pour un exemple spécifique pourquoi, consultez le javadoc pour Thread.interrupt ():

Si ce thread est bloqué dans une invocation de la méthode d'attente (), d'attente (long) ou d'attente (long, int) de la classe d'objets, ou du join (), join (long), join (long, int) , sommeil (long), ou sommeil (long, int), méthodes de cette classe, puis son statut d'interruption sera effacé et il recevra une conception interrompue.

Comme vous pouvez le voir, plus d'une interruption d'Exception pourrait être créée et gérée à mesure que les demandes d'interruption sont traitées, mais uniquement si ce statut d'interruption est conservé.

Autres conseils

La seule façon dont je suis conscient dans lequel Thread.interrupt() est cassé, c'est qu'il ne fait pas réellement ce qu'il semble que cela puisse - il ne peut que réellement couper la parole 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 Concurrence de Java dans la pratique Pour plus de lecture sur son utilisation appropriée et sûre.

Le principal problème avec Thread.interrupt() Est-ce que la plupart des programmeurs ne connaissent pas les pièges cachés et ne l'utilisent pas dans le mauvais sens. Par exemple, lorsque vous gérez l'interruption, il existe des méthodes qui dégager le drapeau (donc le statut se perd).

De plus, l'appel n'interrompra pas toujours le fil immédiatement. Par exemple, lorsqu'il est suspendu dans une routine système, rien ne se passera. En fait, si le fil ne vérifie pas l'indicateur et n'appelle jamais une méthode Java qui jette InterruptException, alors l'interrompre n'aura aucun effet.

Non, ce n'est pas buggy. C'est en fait la base de la façon dont vous arrêtez les fils en Java. Il est utilisé dans le cadre de l'exécuteur de Java.util.concurrent - voir la mise en œuvre de java.util.concurrent.FutureTask.Sync.innerCancel.

Quant à l'échec, je ne l'ai jamais vu échouer et je l'ai utilisé beaucoup.

L'une des raisons non mentionnées est que le signal d'interruption peut être perdu, ce qui rend l'invoquer la méthode thread.interrupt () sans signification. Donc, à moins que votre code dans la méthode thread.run () tourne dans un peu de boucle, le résultat de l'appel thread.interrupt () est incertain.

J'ai remarqué que dans le fil ONE Je exécute DriverManager.getConnection() Lorsqu'il n'y a pas de connexion à la base de données disponible (disons que le serveur est en panne, donc finalement cette ligne lance SQLException) et du fil TWO J'appelle explicitement ONE.interrupt(), alors les deux ONE.interrupted() et ONE.isInterrupted() revenir false même s'il est placé comme première ligne dans le catch{} bloquer où SQLException est traité.

Bien sûr, j'ai réalisé ce problème en mettant en œuvre le sémaphore supplémentaire, mais il est assez gênant, car il s'agit du tout premier problème de ce type de 15 ans de développement Java.

Je suppose que c'est à cause d'un bug dans com.microsoft.sqlserver.jdbc.SQLServerDriver. Et j'ai enquêté plus pour confirmer que l'appel au native La fonction consomme cette exception dans tous les cas, elle se transforme, mais la préserve lorsqu'elle a réussi.

Tomek

Ps j'ai trouvé le problème analogue.

PPS Je joins un très court exemple de ce que j'écrit ci-dessus. La classe enregistrée peut être trouvée dans sqljdbc42.jar. J'ai trouvé ce bug dans les classes construites le 2015-08-20, puis j'ai mis à jour la dernière version disponible (de 2017-01-12) et le bogue 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, votre serveur est en panne ou si vous avez perdu votre connexion nette (qui peut généralement se produire dans l'environnement de production), vous verrez le java.net Printout d'erreur de délai d'attente de socket et du thread interrupted() L'état est perdu.

Le problème n'est pas que l'implémentation n'est pas buggy, mais plutôt votre fil est dans un état inconnu lorsqu'il est interrompu et cela peut conduire à un comportement imprévisible.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top