447 Stimmen

Behandlung von InterruptedException in Java

Was ist der Unterschied zwischen den folgenden Möglichkeiten, eine InterruptedException zu behandeln? Was ist der beste Weg, dies zu tun?

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

...oder:

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

Ich würde auch gerne wissen, in welchen Szenarien diese beiden verwendet werden.

3 Stimmen

600voto

aioobe Punkte 397211

Was ist der Unterschied zwischen den folgenden Arten, ein InterruptedException zu behandeln? Wie ist die beste Möglichkeit, es zu tun?

Sie sind wahrscheinlich hier, um diese Frage zu stellen, weil Sie eine Methode aufgerufen haben, die InterruptedException wirft.

Zunächst sollten Sie throws InterruptedException als das betrachten, was es ist: Ein Teil der Methodensignatur und ein möglicher Ausgang des Aufrufs der Methode, die Sie aufrufen. Beginnen Sie daher damit anzunehmen, dass eine InterruptedException ein vollkommen gültiges Ergebnis des Methodenaufrufs ist.

Jetzt, wenn die Methode, die Sie aufrufen, eine solche Ausnahme wirft, was sollte Ihre Methode tun? Sie können die Antwort herausfinden, indem Sie über Folgendes nachdenken:

Macht es Sinn für die Methode, die Sie implementieren, eine InterruptedException zu werfen? Anders ausgedrückt, ist eine InterruptedException ein sinnvolles Ergebnis beim Aufruf Ihrer Methode?

  • Wenn ja, dann sollte throws InterruptedException Teil der Signatur von Ihrer Methode sein, und Sie sollten die Ausnahme weiterleiten lassen (d.h. gar nicht abfangen).

    Beispiel: Ihre Methode wartet auf einen Wert aus dem Netzwerk, um die Berechnung abzuschließen und ein Ergebnis zurückzugeben. Wenn der blockierende Netzwerkanruf eine InterruptedException wirft, kann Ihre Methode die Berechnung nicht auf normale Weise abschließen. Sie lassen die InterruptedException weiterleiten.

    int computeSum(Server server) throws InterruptedException {
        // Jede unten geworfene InterruptedException wird weitergeleitet
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Wenn nein, dann sollten Sie Ihre Methode nicht mit throws InterruptedException deklarieren und Sie sollten (müssen!) die Ausnahme abfangen. Nun sind in dieser Situation zwei wichtige Dinge zu beachten:

    1. Jemand hat Ihren Thread unterbrochen. Dieser jemand ist wahrscheinlich darauf bedacht, die Operation abzubrechen, das Programm ordnungsgemäß zu beenden oder etwas ähnliches zu tun. Seien Sie höflich zu diesem Jemand und kehren Sie ohne weitere Umstände aus Ihrer Methode zurück.

    2. Auch wenn Ihre Methode in der Lage ist, einen sinnvollen Rückgabewert im Falle einer InterruptedException zu produzieren, kann die Tatsache, dass der Thread unterbrochen wurde, dennoch von Bedeutung sein. Insbesondere kann der aufrufende Code Ihrer Methode daran interessiert sein, ob eine Unterbrechung während der Ausführung Ihrer Methode aufgetreten ist. Sie sollten daher die Tatsache, dass eine Unterbrechung stattgefunden hat, protokollieren, indem Sie die unterbrochene Flagge setzen: Thread.currentThread().interrupt()

    Beispiel: Der Benutzer hat darum gebeten, eine Summe von zwei Werten auszugeben. Das Ausgeben von "Fehler beim Berechnen der Summe" ist akzeptabel, wenn die Summe nicht berechnet werden kann (und viel besser, als das Programm mit einem Stapelüberlauf aufgrund einer InterruptedException zum Absturz zu bringen). Mit anderen Worten, es macht keinen Sinn, diese Methode mit throws InterruptedException zu deklarieren.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Summe: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // Interrupt-Flag setzen
             System.out.println("Fehler beim Berechnen der Summe");
         }
    }

Inzwischen sollte klar sein, dass einfach throw new RuntimeException(e) eine schlechte Idee ist. Es ist nicht sehr höflich gegenüber dem Aufrufer. Sie könnten eine neue Laufzeit-Ausnahme erfinden, aber die Ursache (jemand möchte, dass der Thread mit der Ausführung aufhört) könnte verloren gehen.

Weitere Beispiele:

Implementierung von Runnable: Wie Sie vielleicht festgestellt haben, lässt die Signatur von Runnable.run kein Wiederauswerfen von InterruptedExceptions zu. Nun, Sie haben sich dazu verpflichtet, Runnable zu implementieren, was bedeutet, dass Sie sich dazu verpflichtet haben, mit möglichen InterruptedExceptions umzugehen. Wählen Sie entweder eine andere Schnittstelle, wie z.B. Callable, oder folgen Sie dem zweiten oben genannten Ansatz.

Aufruf von Thread.sleep: Sie versuchen, eine Datei zu lesen, und die Spezifikation besagt, dass Sie es 10 Mal mit je 1 Sekunde Pause versuchen sollten. Sie rufen Thread.sleep(1000) auf. Also müssen Sie mit InterruptedException umgehen. Für eine Methode wie tryToReadFile macht es perfekten Sinn zu sagen: "Wenn ich unterbrochen werde, kann ich meine Aktion des Versuchs, die Datei zu lesen, nicht abschließen". Mit anderen Worten, es macht perfekten Sinn, dass die Methode InterruptedExceptions wirft.

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

Dieser Beitrag wurde als Artikel neu verfasst hier.

0 Stimmen

Was ist das Problem mit der zweiten Methode?

6 Stimmen

Der Artikel besagt, dass Sie interrupt() aufrufen sollten, um den unterbrochenen Status zu erhalten. Was ist der Grund dafür, dies bei einem Thread.sleep() nicht zu tun?

9 Stimmen

Ich stimme nicht überein, dass im Fall, dass Ihr Thread keine Unterbrechungen unterstützt, der Empfang von Unterbrechungen auf eine unerwartete Bedingung hinweist (d. h. ein Programmierfehler, schlechte API-Nutzung), und ein Abbruch mit einer RuntimeException eine angemessene Reaktion (fail-fast) ist. Es sei denn, es ist Teil des allgemeinen Vertrags eines Threads, dass Sie den Betrieb fortsetzen müssen, auch wenn Sie keine Unterbrechungen unterstützen, wenn Sie diese erhalten.

134voto

mR_fr0g Punkte 8084

Wie es der Zufall will, habe ich heute Morgen auf dem Weg zur Arbeit in Java Concurrency in Practice von Brian Goetz darüber gelesen. Grundsätzlich sagt er, dass man eine von drei Optionen wählen sollte

  1. InterruptedException weiterleiten - Deklarieren Sie Ihre Methode so, dass sie die überprüfte InterruptedException wirft, damit der Aufrufer damit umgehen muss.

  2. Die Unterbrechung wiederherstellen - Manchmal kann man InterruptedException nicht werfen. In diesen Fällen sollte man die InterruptedException abfangen und den Unterbrechungsstatus wiederherstellen, indem man die Methode interrupt() auf dem currentThread aufruft, damit der Code weiter oben im Aufrufstapel sehen kann, dass eine Unterbrechung erfolgt ist, und schnell aus der Methode zurückkehren kann. Hinweis: Dies gilt nur, wenn Ihre Methode "versuchen" oder "bestmögliche Bemühungen" Semantik hat, d. h. nichts Kritisches passieren würde, wenn die Methode ihr Ziel nicht erreicht. Zum Beispiel könnten log() oder sendMetric() solche Methoden sein, oder boolean tryTransferMoney(), aber nicht void transferMoney(). Mehr Details finden Sie hier.

  3. Die Unterbrechung innerhalb der Methode ignorieren, aber den Status beim Verlassen wiederherstellen - z. B. über Guavas Uninterruptibles. Uninterruptibles übernehmen den Boilerplate-Code wie im Beispiel für eine nicht abbrechbare Aufgabe in JCIP § 7.1.3.

13 Stimmen

Manchmal kann man InterruptedException nicht werfen - Ich würde sagen, manchmal ist es nicht angebracht, dass die Methode InterruptedExceptions weitergibt. Deine Formulierung scheint zu suggerieren, dass du die InterruptedException immer dann neu werfen solltest, wenn du kannst.

2 Stimmen

Und wenn Sie Kapitel 7 lesen, werden Sie feststellen, dass es einige Feinheiten gibt, wie die in Listing 7.7, wo eine nicht abbrechbare Aufgabe das Unterbrechen nicht sofort wiederherstellt, sondern erst, nachdem sie abgeschlossen ist. Außerdem hat Goetz viele Co-Autoren für dieses Buch...

1 Stimmen

Ich mag deine Antwort, weil sie prägnant ist, aber ich glaube, sie sollte auch angeben, dass in diesem Fall sanft und schnell aus der Funktion zurückgekehrt werden sollte. Stellen Sie sich eine lange (oder unendliche) Schleife mit einem Thread.sleep() in der Mitte vor. Der Catch-Block der InterruptedException sollte Thread.currentThread().interrupt() und aus der Schleife ausbrechen.

28voto

Grodriguez Punkte 21042

Was versuchst du zu tun?

Die InterruptedException wird ausgelöst, wenn ein Thread wartet oder schläft und ein anderer Thread ihn unterbricht, indem er die interrupt-Methode in der Klasse Thread verwendet. Wenn du also diese Ausnahme abfängst, bedeutet das, dass der Thread unterbrochen wurde. Normalerweise macht es keinen Sinn, Thread.currentThread().interrupt(); erneut aufzurufen, es sei denn, du möchtest den "unterbrochenen" Status des Threads von einem anderen Ort aus überprüfen.

Was deine andere Option betrifft, eine RuntimeException zu werfen, scheint das keine sehr kluge Sache zu sein (wer wird das abfangen? wie wird damit umgegangen?), aber es ist schwer, mehr ohne zusätzliche Informationen zu sagen.

20 Stimmen

Der Aufruf von Thread.currentThread().interrupt() setzt das unterbrochene Flag (erneut), was tatsächlich nützlich ist, wenn wir sicherstellen wollen, dass das Unterbrechen auf einer höheren Ebene bemerkt und verarbeitet wird.

1 Stimmen

@Péter: Ich habe gerade die Antwort aktualisiert, um dies zu erwähnen. Danke.

17voto

nedruod Punkte 1094

Die richtige Standardauswahl besteht darin, InterruptedException Ihrer Wurfliste hinzuzufügen. Ein Interrupt zeigt an, dass ein anderer Thread wünscht, dass Ihr Thread endet. Der Grund für dieses Anliegen wird nicht offensichtlich gemacht und ist vollständig kontextabhängig, daher sollten Sie, wenn Sie keine zusätzlichen Informationen haben, davon ausgehen, dass es sich nur um einen freundlichen Abschluss handelt, und alles, was diesen Abschluss verhindert, ist eine unfreundliche Reaktion.

Java wirft nicht zufällig InterruptedException's, alle Ratschläge werden Ihre Anwendung nicht beeinflussen, aber ich bin auf einen Fall gestoßen, in dem Entwickler, die der "schlucken"-Strategie folgten, sehr unbequem wurden. Ein Team hatte eine große Anzahl von Tests entwickelt und Thread.Sleep häufig verwendet. Jetzt begannen wir, die Tests auf unserem CI-Server auszuführen, und manchmal blieben sie aufgrund von Mängeln im Code in permanenten Wartezeiten stecken. Um die Situation noch schlimmer zu machen, schloss der Versuch, den CI-Job abzubrechen, nie richtig, weil der Thread.Interrupt, der vorgesehen war, um den Test abzubrechen, den Job nicht abbrach. Wir mussten uns anmelden und die Prozesse manuell beenden.

Kurz gesagt, wenn Sie einfach die InterruptedException werfen, entsprechen Sie der Standardabsicht, dass Ihr Thread enden sollte. Wenn Sie InterruptedException nicht zu Ihrer Wurfliste hinzufügen können, würde ich sie in eine RuntimeException einwickeln.

Es gibt ein sehr rationales Argument dafür, dass InterruptedException selbst eine RuntimeException sein sollte, da dies eine bessere "Standard"-Behandlung fördern würde. Es ist keine RuntimeException nur, weil die Designer an einer kategorischen Regel festhielten, dass eine RuntimeException einen Fehler in Ihrem Code darstellen sollte. Da eine InterruptedException nicht direkt aus einem Fehler in Ihrem Code entsteht, ist dies nicht der Fall. Aber die Realität ist, dass eine InterruptedException oft auftritt, weil es einen Fehler in Ihrem Code gibt (z.B. eine Endlosschleife, ein Deadlock), und der Interrupt ist die Methode eines anderen Threads, um mit diesem Fehler umzugehen.

Wenn Sie wissen, dass es rationale Aufräumarbeiten gibt, dann führen Sie sie durch. Wenn Sie eine tiefere Ursache für den Interrupt kennen, können Sie eine umfassendere Behandlung übernehmen.

Also sollten Ihre Optionen zur Behandlung dieser Liste folgen:

  1. Standardmäßig zu Würfen hinzufügen.
  2. Wenn das Hinzufügen zu den Würfen nicht erlaubt ist, werfen Sie RuntimeException(e). (Beste Wahl unter mehreren schlechten Optionen)
  3. Nur wenn Sie eine explizite Ursache des Interrupts kennen, behandeln Sie ihn wie gewünscht. Wenn Ihre Bearbeitung lokal für Ihre Methode ist, setzen Sie den Interrupt durch Aufruf von Thread.currentThread().interrupt() zurück.

0 Stimmen

+1 für Ihre Zusammenfassung (und auch für die Anmerkung, dass InterruptedException von Anfang an eine RuntimeException hätte sein sollen). Thread.currentThread().interrupt() als "Standardbehandlungsmechanismus" kann sehr gefährlich sein und zu unbeabsichtigten Dauerschleifen führen. Daher wird es zu Recht nur als letzte Möglichkeit erwähnt, die angewendet werden sollte, wenn Sie wissen, was Sie tun (und warum).

0 Stimmen

Btw, eine "elegante" Möglichkeit, Option 2 zu implementieren, besteht darin, der Methode die @SneakyThrows(InterruptedException.class) Lombok-Annotation hinzuzufügen :)

14voto

Nathan Hughes Punkte 90344

Für mich ist das entscheidende an diesem Punkt: Eine InterruptedException ist nichts, was schief läuft, sie ist lediglich der Thread, der tut, was du ihm gesagt hast. Daher ergibt es überhaupt keinen Sinn, sie in einer RuntimeException erneut zu werfen.

In vielen Fällen macht es Sinn, eine Ausnahme in einer RuntimeException erneut zu werfen, wenn du sagst, ich weiß nicht, was hier schief gelaufen ist und ich kann nichts tun, um es zu beheben, ich möchte es einfach aus dem aktuellen Verarbeitungsfluss herausholen und auf den applikationsweiten Ausnahme-Handler stoßen lassen, damit er protokollieren kann. Das gilt jedoch nicht für eine InterruptedException, der Thread reagiert einfach auf den Aufruf von interrupt(), er wirft die InterruptedException, um den Thread-Prozess rechtzeitig abzubrechen.

Also leite die InterruptedException weiter, oder behandle sie intelligent (an einem Ort, an dem sie erreicht hat, was sie tun sollte) und setze das Interrupt-Flag zurück. Beachte, dass das Interrupt-Flag gelöscht wird, wenn die InterruptedException geworfen wird; die Annahme der JDK-Entwickler ist, dass das Fangen der Ausnahme bedeutet, dass sie behandelt wird, daher wird das Flag standardmäßig gelöscht.

Also definitiv ist der erste Weg besser, das zweite in der Frage gepostete Beispiel ist nicht nützlich, es sei denn, du erwartest nicht, dass der Thread tatsächlich unterbrochen wird, und die Unterbrechung stellt einen Fehler dar.

Hier ist eine Antwort, die ich verfasst habe, die beschreibt, wie Unterbrechungen funktionieren, mit einem Beispiel. Im Beispielcode kannst du sehen, wo die InterruptedException verwendet wird, um aus einer While-Schleife in der run-Methode des Runnable auszusteigen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X