Gestion de l'interruption d'interception en Java

317

Quelle est la différence entre les modes de manipulation suivants InterruptedException? Quelle est la meilleure façon de procéder?

try{
 //...
} catch(InterruptedException e) { 
   Thread.currentThread().interrupt(); 
}

OU

try{
 //...
} catch(InterruptedException e) {
   throw new RuntimeException(e);
}

EDIT: Je voudrais également savoir dans quels scénarios ces deux sont utilisés.

devnull
la source

Réponses:

436

Quelle est la différence entre les manières suivantes de gérer InterruptedException? Quelle est la meilleure façon de procéder?

Vous êtes probablement venu poser cette question parce que vous avez appelé une méthode qui lance InterruptedException.

Tout d'abord, vous devriez voir throws InterruptedExceptionde quoi il s'agit: une partie de la signature de la méthode et un résultat possible de l'appel de la méthode que vous appelez. Commencez donc par comprendre que an InterruptedExceptionest un résultat parfaitement valide de l'appel de méthode.

Maintenant, si la méthode que vous appelez lève une telle exception, que devrait faire votre méthode? Vous pouvez trouver la réponse en réfléchissant à ce qui suit:

Est-il logique que la méthode que vous implémentez lance un InterruptedException? Autrement dit, est-ce InterruptedExceptionun résultat raisonnable lorsque vous appelez votre méthode?

  • Si oui , alors throws InterruptedExceptiondevrait faire partie de votre signature de méthode, et vous devriez laisser l'exception se propager (c'est-à-dire ne pas l'attraper du tout).

    Exemple : votre méthode attend une valeur du réseau pour terminer le calcul et retourner un résultat. Si l'appel réseau bloquant lance un, InterruptedExceptionvotre méthode ne peut pas terminer le calcul de manière normale. Vous laissez la InterruptedExceptionpropagation.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Si non , vous ne devez pas déclarer votre méthode avec throws InterruptedExceptionet vous devez (devez!) Intercepter l'exception. Maintenant, deux choses sont importantes à garder à l'esprit dans cette situation:

    1. Quelqu'un a interrompu votre fil. Que quelqu'un est probablement impatient d'annuler l'opération, de terminer le programme avec élégance ou autre chose. Vous devez être poli avec cette personne et revenir de votre méthode sans plus tarder.

    2. Même si votre méthode parvient à produire une valeur de retour raisonnable en cas de problème, InterruptedExceptionle fait que le thread a été interrompu peut toujours être important. En particulier, le code qui appelle votre méthode peut être intéressé à savoir si une interruption s'est produite lors de l'exécution de votre méthode. Vous devez donc consigner le fait qu'une interruption a eu lieu en définissant le drapeau interrompu:Thread.currentThread().interrupt()

    Exemple : L'utilisateur a demandé d'imprimer une somme de deux valeurs. L'impression " Failed to compute sum" est acceptable si la somme ne peut pas être calculée (et bien mieux que de laisser le programme planter avec une trace de pile en raison d'un InterruptedException). En d'autres termes, il n'est pas logique de déclarer cette méthode avec throws InterruptedException.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Sum: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // set interrupt flag
             System.out.println("Failed to compute sum");
         }
    }

À présent, il devrait être clair que faire throw new RuntimeException(e)est une mauvaise idée. Ce n'est pas très poli avec l'appelant. Vous pouvez inventer une nouvelle exception d'exécution, mais la cause première (quelqu'un veut que le thread arrête l'exécution) peut être perdue.

Autres exemples:

ImplémentationRunnable : Comme vous l'avez peut-être découvert, la signature de Runnable.runne permet pas de relancer InterruptedExceptions. Eh bien, vous vous êtes inscrit sur la mise en œuvre Runnable, ce qui signifie que vous vous êtes inscrit pour faire face à possible InterruptedExceptions. Choisissez une interface différente, telle que Callable, ou suivez la deuxième approche ci-dessus.

 

AppelThread.sleep : vous essayez de lire un fichier et la spécification indique que vous devriez essayer 10 fois avec 1 seconde entre les deux. Vous appelez Thread.sleep(1000). Donc, vous devez vous en occuper InterruptedException. Pour une méthode comme celle- tryToReadFileci, il est parfaitement logique de dire: "Si je suis interrompu, je ne peux pas terminer mon action consistant à essayer de lire le fichier" . En d'autres termes, il est parfaitement logique que la méthode lance InterruptedExceptions.

String tryToReadFile(File f) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        if (f.exists())
            return readFile(f);
        Thread.sleep(1000);
    }
    return null;
}

Ce message a été réécrit en tant qu'article ici .

aioobe
la source
Quel est le problème avec la deuxième méthode?
blitzkriegz
4
L'article dit que vous devez appeler interrupt()pour conserver le statut interrompu. Quelle est la raison derrière ne pas faire ça sur un Thread.sleep()?
oksayt
7
Je ne suis pas d'accord, dans le cas où votre thread ne prend pas en charge les interruptions, toute interruption reçue indique une condition inattendue (c'est-à-dire une erreur de programmation, une mauvaise utilisation de l'API), et le renflouement avec une RuntimeException est une réponse appropriée (fail-fast ). Sauf si cela fait partie du contrat général d'un thread, même si vous ne supportez pas les interruptions, vous devez continuer à fonctionner lorsque vous les recevez.
amoe
12
Appeler des méthodes qui lèvent des InterruptedExceptions et disent que vous "ne supportez pas les interruptions" semble être une programmation bâclée et un bogue attend. Placé différemment; Si vous appelez une méthode déclarée pour lever InterruptedException, cela ne devrait pas être une condition inattendue si cette méthode lève en fait une telle exception.
aioobe
1
@MartiNito, non, appeler Thread.currentThread.interrupt()un InterruptedExceptionbloc catch ne définira que le drapeau d'interruption. Cela ne fera pas InterruptedExceptionjeter / attraper un autre dans un InterruptedExceptionbloc de capture externe .
aioobe
98

Il se trouve que je lisais ce sujet ce matin en allant travailler dans Java Concurrency In Practice de Brian Goetz. Fondamentalement, il dit que vous devez faire trois choses

  1. Propagez laInterruptedException - Déclarez votre méthode pour lancer la vérification InterruptedExceptionafin que votre appelant doive y faire face.

  2. Restaurer l'interruption - Parfois, vous ne pouvez pas lancer InterruptedException. Dans ces cas, vous devez intercepter InterruptedExceptionet restaurer l'état d'interruption en appelant la interrupt()méthode sur le currentThreadafin que le code situé plus haut dans la pile d'appels puisse voir qu'une interruption a été émise et revenir rapidement de la méthode. Remarque: ceci n'est applicable que lorsque votre méthode a une sémantique "try" ou "best effort", c'est-à-dire que rien de critique ne se produira si la méthode n'atteint pas son objectif. Par exemple, log()ou sendMetric()peut être une telle méthode, ou boolean tryTransferMoney(), mais pas void transferMoney(). Voir ici pour plus de détails.

  3. Ignorez l'interruption dans la méthode, mais restaurez le statut à la sortie - par exemple via Guava Uninterruptibles. Uninterruptiblesreprendre le code passe-partout comme dans l'exemple de tâche non annulable dans JCIP § 7.1.3.
mR_fr0g
la source
10
"Parfois, vous ne pouvez pas lever InterruptedException" - je dirais que parfois, il n'est pas approprié que la méthode propage les InterruptedExceptions. Votre formulation semble suggérer que vous devez à nouveau lever l'exception InterruptedException chaque fois que vous le pouvez .
aioobe
1
Et si vous arrivez à lire le chapitre 7, vous verrez qu'il y a quelques subtilités à cela, comme celles du Listing 7.7, où une tâche non annulable ne restaure pas immédiatement l'interruption , mais seulement après qu'elle soit terminée. Goetz a également de nombreux co-auteurs de ce livre ...
Fizz
1
J'aime votre réponse car elle est concise, mais je pense qu'elle devrait également indiquer de revenir doucement et rapidement de votre fonction dans le deuxième cas. Imaginez une boucle longue (ou infinie) avec un Thread.sleep () au milieu. La capture de l'exception InterruptedException doit appeler Thread.currentThread (). Interrupt () et sortir de la boucle.
Yann Vo
@YannVo J'ai modifié la réponse pour appliquer votre suggestion.
leventov
19

Qu'essayez-vous de faire?

Le InterruptedExceptionest levé lorsqu'un thread est en attente ou en sommeil et qu'un autre thread l'interrompt en utilisant la interruptméthode en classe Thread. Donc, si vous interceptez cette exception, cela signifie que le thread a été interrompu. Habituellement, il est inutile d'appeler à Thread.currentThread().interrupt();nouveau, sauf si vous voulez vérifier l'état «interrompu» du thread ailleurs.

En ce qui concerne votre autre option de lancer un RuntimeException, cela ne semble pas une chose très sage à faire (qui va attraper cela? Comment sera-t-il géré?) Mais il est difficile d'en dire plus sans informations supplémentaires.

Grodriguez
la source
14
L'appel Thread.currentThread().interrupt()définit à nouveau l'indicateur interrompu, ce qui est utile en effet si nous voulons nous assurer que l'interruption est remarquée et traitée à un niveau supérieur.
Péter Török
1
@ Péter: Je venais de mettre à jour la réponse pour le mentionner. Merci.
Grodriguez
12

Pour moi, l'élément clé à ce sujet est: une InterruptedException n'est pas quelque chose qui ne va pas, c'est le fil qui fait ce que vous lui avez dit de faire. Par conséquent, le relancer enveloppé dans une RuntimeException n'a aucun sens.

Dans de nombreux cas, il est logique de renvoyer une exception enveloppée dans une exception RuntimeException lorsque vous dites, je ne sais pas ce qui s'est mal passé ici et je ne peux rien faire pour le corriger, je veux juste qu'il sorte du flux de traitement actuel et appuyez sur le gestionnaire d'exceptions à l'échelle de l'application que j'ai pour qu'il puisse le journaliser. Ce n'est pas le cas avec une InterruptedException, c'est juste le thread qui répond à avoir appelé interrupt (), il lance l'InterruptedException afin d'aider à annuler le traitement du thread en temps opportun.

Alors propagez l'InterruptedException, ou mangez-le intelligemment (c'est-à-dire à un endroit où il aura accompli ce qu'il était censé faire) et réinitialisez le drapeau d'interruption. Notez que l'indicateur d'interruption est effacé lorsque l'interruptionException est levée; l'hypothèse que les développeurs de la bibliothèque Jdk font est que la capture de l'exception revient à la gérer, donc par défaut le drapeau est effacé.

Donc, la première façon est certainement meilleure, le deuxième exemple publié dans la question n'est utile que si vous ne vous attendez pas à ce que le thread soit réellement interrompu, et l'interrompre équivaut à une erreur.

Voici une réponse que j'ai écrite décrivant le fonctionnement des interruptions, avec un exemple . Vous pouvez voir dans l'exemple de code où il utilise l'InterruptedException pour sortir d'une boucle while dans la méthode d'exécution de Runnable.

Nathan Hughes
la source
10

Le choix par défaut correct est d'ajouter InterruptedException à votre liste de lancers. Une interruption indique qu'un autre thread souhaite que votre thread se termine. La raison de cette demande n'est pas rendue évidente et est entièrement contextuelle, donc si vous n'avez pas de connaissances supplémentaires, vous devez supposer que c'est juste un arrêt amical, et tout ce qui évite cet arrêt est une réponse non amicale.

Java ne lèvera pas au hasard InterruptedException, tous les conseils n'affecteront pas votre application, mais j'ai rencontré un cas où le développeur suivant la stratégie "swallow" est devenu très gênant. Une équipe avait développé un large éventail de tests et utilisé beaucoup Thread.Sleep. Maintenant, nous avons commencé à exécuter les tests sur notre serveur CI, et parfois, en raison de défauts dans le code, nous nous retrouvions coincés dans des attentes permanentes. Pour aggraver la situation, lorsque vous tentiez d'annuler le travail CI, il ne se fermait jamais car le thread. L'interruption qui était destinée à abandonner le test n'a pas interrompu le travail. Nous avons dû nous connecter à la boîte et tuer manuellement les processus.

Donc, pour faire court, si vous lancez simplement l'exception InterruptedException, vous correspondez à l'intention par défaut de la fin de votre thread. Si vous ne pouvez pas ajouter InterruptedException à votre liste de lancers, je l'envelopperais dans une RuntimeException.

Il y a un argument très rationnel à faire valoir que InterruptedException devrait être une RuntimeException elle-même, car cela encouragerait une meilleure gestion "par défaut". Ce n'est pas une RuntimeException uniquement parce que les concepteurs se sont tenus à une règle catégorique selon laquelle une RuntimeException devrait représenter une erreur dans votre code. Puisqu'une InterruptedException ne résulte pas directement d'une erreur dans votre code, ce n'est pas le cas. Mais la réalité est que souvent une InterruptedException se produit parce qu'il y a une erreur dans votre code, (c'est-à-dire une boucle sans fin, un verrou mortel), et l'Interruption est une autre méthode d'un autre thread pour traiter cette erreur.

Si vous savez qu'il y a un nettoyage rationnel à faire, faites-le. Si vous connaissez une cause plus profonde de l'interruption, vous pouvez prendre en charge une gestion plus complète.

Donc, en résumé, vos choix de manipulation doivent suivre cette liste:

  1. Par défaut, ajoutez aux lancers.
  2. S'il n'est pas autorisé à ajouter aux lancers, lancez RuntimeException (e). (Meilleur choix de plusieurs mauvaises options)
  3. Seulement lorsque vous connaissez une cause explicite de l'interruption, gérez-la comme vous le souhaitez. Si votre gestion est locale à votre méthode, réinitialisez-la interrompue par un appel à Thread.currentThread (). Interrupt ().
nedruod
la source
3

Je voulais juste ajouter une dernière option à ce que la plupart des gens et des articles mentionnent. Comme l'a indiqué mR_fr0g, il est important de gérer correctement l'interruption soit par:

  • Propager l'exception InterruptException

  • Restaurer l'état d'interruption sur le thread

Ou en plus:

  • Gestion personnalisée des interruptions

Il n'y a rien de mal à gérer l'interruption de manière personnalisée selon vos circonstances. Comme une interruption est une demande de résiliation, par opposition à une commande forcée, il est parfaitement valide de terminer un travail supplémentaire pour permettre à l'application de traiter la demande avec élégance. Par exemple, si un thread est en veille, en attente d'E / S ou d'une réponse matérielle, lorsqu'il reçoit l'interruption, il est parfaitement valide de fermer correctement toutes les connexions avant de terminer le thread.

Je recommande fortement de comprendre le sujet, mais cet article est une bonne source d'informations: http://www.ibm.com/developerworks/java/library/j-jtp05236/

TheIT
la source
0

Je dirais que dans certains cas, c'est ok de ne rien faire. Ce n'est probablement pas quelque chose que vous devriez faire par défaut, mais au cas où il ne devrait y avoir aucun moyen pour que l'interruption se produise, je ne sais pas quoi faire d'autre (probablement une erreur de journalisation, mais cela n'affecte pas le flux du programme).

Un cas serait dans le cas où vous avez une file d'attente de tâches (blocage). Dans le cas où vous avez un thread démon qui gère ces tâches et que vous n'interrompez pas le thread par vous-même (à ma connaissance, le jvm n'interrompt pas les threads du démon à l'arrêt jvm), je ne vois aucun moyen pour que l'interruption se produise, et donc cela pourrait être juste ignoré. (Je sais qu'un thread démon peut être tué par le jvm à tout moment et ne convient donc pas dans certains cas).

EDIT: Un autre cas peut être des blocs gardés, au moins basés sur le tutoriel d'Oracle à: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Bjarne Boström
la source
C'est un mauvais conseil. J'ai beaucoup de mal à penser à n'importe quelle tâche qui est si importante qu'elle ignore les interruptions. Oracle était probablement juste paresseux et ne voulait pas ajouter (plus) de fouillis à l'invocation de Thread.sleep (). À moins que vous ne sachiez pourquoi votre thread est interrompu, vous devez arrêter tout ce que vous faites (soit retourner, soit lever une exception pour aider votre thread à mourir le plus rapidement possible).
Ajax
C'est pourquoi je dis parfois. Aussi parce que ce n'était pas encore suggéré et c'est parfaitement bien dans certains cas à mon avis. Je suppose que cela dépend du type de logiciel que vous créez, mais si vous avez un thread de travail 24h / 24 et 7j / 7 qui ne devrait jamais s'arrêter (à part l'arrêt de la JVM), je traiterais les interruptions, par exemple en prenant un élément d'une BlockingQueue comme une erreur (c.-à-d. journal) car "cela ne devrait pas arriver" et réessayez. Bien sûr, j'aurais des drapeaux séparés pour vérifier la fin du programme. Dans certains de mes programmes, l'arrêt de la machine virtuelle Java n'est pas quelque chose qui devrait se produire en fonctionnement normal.
Bjarne Boström
Ou en d'autres termes: à mon avis, il est préférable de risquer d'avoir des instructions de journal d'erreurs supplémentaires (et, par exemple, d'envoyer des messages sur des journaux d'erreurs) que d'avoir une application qui devrait fonctionner 24h / 24 et 7j / 7 pour s'arrêter en raison d'une exception d'interruption, pour laquelle Je ne vois aucun moyen de se produire dans des conditions normales.
Bjarne Boström
Hm, eh bien, vos cas d'utilisation peuvent différer, mais dans un monde idéal, vous ne vous arrêtez pas simplement parce que vous avez été interrompu. Vous arrêtez de retirer le travail de la file d'attente et laissez ce fil mourir. Si le planificateur de threads est également interrompu et doit s'arrêter, la JVM se termine et sa partie est terminée. En particulier, si vous envoyez kill -9 ou si vous essayez de supprimer votre serveur de messagerie ou autre chose, il vous ignorera jusqu'à ce que vous forciez à quitter, empêchant ainsi d'autres parties de votre code de s'arrêter normalement. Force kill lors de l'écriture sur disque ou db, et je suis sûr que des choses merveilleuses se produiront.
Ajax