Obsługa wyjątku InterruptedException w Javie

317

Jaka jest różnica między następującymi sposobami postępowania InterruptedException? Jak najlepiej to zrobić?

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

LUB

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

EDYCJA: Chciałbym również wiedzieć, w których scenariuszach te dwa są używane.

diabelnie
źródło

Odpowiedzi:

436

Jaka jest różnica między następującymi sposobami obsługi InterruptedException? Jak najlepiej to zrobić?

Prawdopodobnie przyszedłeś zadać to pytanie, ponieważ wywołałeś metodę rzucającą InterruptedException.

Przede wszystkim powinieneś zobaczyć, throws InterruptedExceptionco to jest: Część podpisu metody i możliwy wynik wywołania metody, którą wywołujesz. Zacznijmy od uwzględnienia faktu, że an InterruptedExceptionjest całkowicie poprawnym wynikiem wywołania metody.

Teraz, jeśli wywoływana metoda generuje taki wyjątek, co powinna zrobić twoja metoda? Możesz znaleźć odpowiedź, myśląc o następujących kwestiach:

Czy ma sens metoda, którą wdrażasz, aby rzucić InterruptedException? Innymi słowy, jest InterruptedExceptionto rozsądny wynik podczas wywoływania swój sposób?

  • Jeśli Tak , to throws InterruptedExceptionpowinno być częścią Twojego podpisu metody i należy pozwolić propagować wyjątku (czyli nie złapać go w ogóle).

    Przykład : Twoja metoda czeka na wartość z sieci, która zakończy obliczenia i zwróci wynik. Jeśli zostanie wywołane blokujące połączenie sieciowe, InterruptedExceptionmetoda nie może zakończyć obliczeń w normalny sposób. Zezwalasz na InterruptedExceptionpropagację.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Jeśli nie , nie powinieneś deklarować swojej metody throws InterruptedExceptioni powinieneś (musisz!) Złapać wyjątek. Teraz w tej sytuacji należy pamiętać o dwóch rzeczach:

    1. Ktoś przerwał twój wątek. Że ktoś prawdopodobnie chce anulować operację, zakończyć program z wdziękiem lub cokolwiek innego. Powinieneś być uprzejmy wobec tej osoby i powrócić ze swojej metody bez zbędnych ceregieli.

    2. Nawet jeśli twoja metoda jest w stanie wygenerować sensowną wartość zwrotną w przypadku InterruptedExceptionprzerwania wątku, może nadal mieć znaczenie. W szczególności kod wywołujący metodę może być zainteresowany tym, czy wystąpiła przerwa podczas wykonywania metody. Powinieneś zatem zarejestrować fakt, że doszło do zakłócenia, ustawiając flagę przerwania:Thread.currentThread().interrupt()

    Przykład : użytkownik poprosił o wydrukowanie sumy dwóch wartości. Drukowanie „ Failed to compute sum” jest dopuszczalne, jeśli suma nie może być obliczona (i znacznie lepiej niż pozwolić programowi zawiesić się ze śladem stosu z powodu InterruptedException). Innymi słowy, nie ma sensu deklarować tej metody za pomocą 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");
         }
    }

Do tej pory powinno być jasne, że samo robienie throw new RuntimeException(e)jest złym pomysłem. Dzwoniący nie jest zbyt uprzejmy. Możesz wymyślić nowy wyjątek czasu wykonywania, ale główna przyczyna (ktoś chce, aby wątek zatrzymał wykonywanie) może zostać utracona.

Inne przykłady:

WdrażanieRunnable : jak być może Runnable.runodkryłeś , podpis nie pozwala na ponowne rzucanie InterruptedExceptions. Cóż, pan zapisał się na realizacji Runnable, co oznacza, że jesteś zarejestrowany do czynienia z możliwości InterruptedExceptions. Wybierz inny interfejs, taki jak Callablelub zastosuj drugie podejście opisane powyżej.

 

DzwonienieThread.sleep : Próbujesz odczytać plik, a specyfikacja mówi, że powinieneś spróbować 10 razy z 1 sekundą pomiędzy nimi. Zadzwonić Thread.sleep(1000). Musisz sobie z tym poradzić InterruptedException. W przypadku metody takiej jak tryToReadFilesensowne jest powiedzenie: „Jeśli przeszkadzam, nie mogę dokończyć próby odczytania pliku” . Innymi słowy, metoda rzucania ma sens 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;
}

Ten post został przepisany jako artykuł tutaj .

aioobe
źródło
Na czym polega problem z drugą metodą?
blitzkriegz
4
Artykuł mówi, że powinieneś zadzwonić, interrupt()aby zachować status przerwania. Jaki jest powód, dla którego nie robisz tego na urządzeniu Thread.sleep()?
oksayt
7
Nie zgadzam się, w przypadku gdy twój wątek nie obsługuje przerwań, wszelkie odebrane przerwie wskazują na nieoczekiwany warunek (tj. Błąd programowania, złe użycie API), a ratowanie za pomocą RuntimeException jest odpowiednią odpowiedzią (szybkie działanie ). O ile nie jest to uważane za część ogólnej umowy wątku, który nawet jeśli nie obsługuje przerwań, musi być kontynuowany po ich otrzymaniu.
amoe
12
Wywoływanie metod, które zgłaszają wyjątki InterruptedException i mówią, że „nie obsługujesz przerwań”, wygląda na niechlujne programowanie i błąd, który czeka na wystąpienie. Inaczej mówiąc; Jeśli wywołasz metodę, która jest zadeklarowana jako zgłaszająca wyjątek InterruptedException, nie powinno to być nieoczekiwanym warunkiem, jeśli metoda ta faktycznie zgłasza taki wyjątek.
aioobe,
1
@ MartiNito, nie, wywołanie Thread.currentThread.interrupt()w InterruptedExceptionbloku catch ustawi tylko flagę przerwania. Nie spowoduje InterruptedExceptionto wyrzucenia / złapania innego w zewnętrzną stronęInterruptedException bloku zaczepu.
aioobe
97

Tak się składa, że ​​właśnie czytałem o tym dziś rano w drodze do pracy w Java Concurrency In Practice autorstwa Briana Goetza. Zasadniczo mówi, że powinieneś zrobić jedną z trzech rzeczy

  1. PropagujInterruptedException - Zadeklaruj metodę rzucenia zaznaczenia InterruptedException, aby rozmówca musiał sobie z tym poradzić.

  2. Przywróć przerwanie - Czasami nie możesz rzucać InterruptedException. W takich przypadkach powinieneś złapaćInterruptedException i przywrócić status przerwania, wywołując interrupt()metodę na currentThreadtak, aby kod znajdujący się wyżej na stosie wywołań mógł zobaczyć, że wystąpiło przerwanie, i szybko wrócić z metody. Uwaga: ma to zastosowanie tylko wtedy, gdy twoja metoda ma semantykę „spróbuj” lub „najlepiej się wysila”, tzn. Nic krytycznego by się nie wydarzyło, gdyby metoda nie osiągnęła swojego celu. Na przykład, log()lub sendMetric()może być taką metodą, lub boolean tryTransferMoney(), ale nie void transferMoney(). Zobacz tutaj po więcej szczegółów.

  3. Zignoruj ​​przerwanie w metodzie, ale przywróć status po wyjściu - np. Za pomocą Guawy Uninterruptibles. Uninterruptiblesprzejąć kod płyty wzorcowej jak w przykładzie Zadania niepodlegającego anulowaniu w JCIP § 7.1.3.
mR_fr0g
źródło
10
„Czasami nie można zgłosić wyjątku InterruptedException - powiedziałbym, że czasami metoda ta nie jest odpowiednia do zgłaszania wyjątków InterruptedException. Twoje sformułowanie wydaje się sugerować, że powinieneś powrócić do wyjątku InterruptedException, kiedy tylko możesz .
aioobe,
1
A jeśli przejdziesz do rozdziału 7, zobaczysz, że są w tym pewne subtelności, takie jak te z Listingu 7.7, w których nieodwołalne zadanie nie przywraca przerwania od razu , ale dopiero po jego zakończeniu. Również Goetz ma wielu współautorów tej książki ...
Fizz
1
Podoba mi się twoja odpowiedź, ponieważ jest zwięzła, jednak uważam, że powinna ona również delikatnie i szybko powrócić z twojej funkcji w drugim przypadku. Wyobraź sobie długą (lub nieskończoną) pętlę z Thread.sleep () pośrodku. Złapanie wyjątku InterruptedException powinno wywołać Thread.currentThread (). Interrupt () i wyjść z pętli.
Yann Vo
@YannVo Zredagowałem odpowiedź, aby zastosować Twoją sugestię.
leventov
18

Co próbujesz zrobić?

InterruptedExceptionJest generowany, gdy wątek jest oczekiwanie lub spania, a kolejne przerwania nitki to stosując interruptmetodę w klasie Thread. Jeśli więc złapiesz ten wyjątek, oznacza to, że wątek został przerwany. Zwykle nie ma sensu dzwonićThread.currentThread().interrupt(); ponownie, chyba że chcesz sprawdzić status „przerwanego” wątku z innego miejsca.

Jeśli chodzi o twoją inną opcję rzucania RuntimeException, nie wydaje się to zbyt mądrym posunięciem (kto to złapie? Jak to będzie zrobione?), Ale trudno powiedzieć więcej bez dodatkowych informacji.

Grodriguez
źródło
14
Wywołanie Thread.currentThread().interrupt()ustawia flagę przerwania (ponownie), co jest przydatne, jeśli chcemy mieć pewność, że przerwanie zostanie zauważone i przetworzone na wyższym poziomie.
Péter Török,
1
@ Péter: Właśnie aktualizowałem odpowiedź, aby o tym wspomnieć. Dzięki.
Grodriguez,
12

Dla mnie kluczową rzeczą jest to: Przerwany Wyjątek nie jest niczym złym, jest to wątek robiąc to, co mu kazałeś. Dlatego ponowne włączenie go w RuntimeException nie ma sensu.

W wielu przypadkach sensowne jest ponowne zwrócenie wyjątku zawartego w RuntimeException, gdy mówisz: Nie wiem, co poszło tutaj źle i nie mogę nic zrobić, aby to naprawić, po prostu chcę, aby wyszło z bieżącego przepływu przetwarzania i naciśnij dowolny program obsługi wyjątków dla całej aplikacji, aby go zarejestrować. Nie jest tak w przypadku InterruptedException, to tylko wątek reagujący na wywołanie interrupt (), rzuca InterruptedException, aby pomóc anulować przetwarzanie wątku w odpowiednim czasie.

Dlatego rozpowszechnij wyjątek InterruptedException lub zjedz go inteligentnie (tzn. W miejscu, w którym osiągnie to, co było zamierzone) i zresetuj flagę przerwania. Zauważ, że flaga przerwania jest usuwana, gdy zostanie zgłoszony wyjątek InterruptedException; założeniem twórców bibliotek Jdk jest to, że wyłapanie wyjątku sprowadza się do jego obsługi, więc domyślnie flaga jest kasowana.

Zdecydowanie więc pierwszy sposób jest lepszy, drugi opublikowany przykład w pytaniu nie jest przydatny, chyba że nie spodziewasz się, że wątek rzeczywiście zostanie przerwany, a przerwanie go oznacza błąd.

Oto odpowiedź, którą napisałem, opisując na przykład, jak działają przerwania . Możesz zobaczyć w przykładowym kodzie, w którym używa on InterruptedException do zwolnienia z pętli while w metodzie run Runnable.

Nathan Hughes
źródło
10

Prawidłowym domyślnym wyborem jest dodanie InterruptedException do listy rzutów. Przerwanie wskazuje, że inny wątek chce, aby Twój koniec się zakończył. Powód tego żądania nie jest oczywisty i jest całkowicie kontekstowy, więc jeśli nie posiadasz żadnej dodatkowej wiedzy, powinieneś założyć, że jest to tylko przyjazne zamknięcie, a wszystko, co pozwala uniknąć tego zamknięcia, jest nieprzyjazną odpowiedzią.

Java nie wyrzuci losowo wyjątków InterruptedException, wszystkie porady nie wpłyną na twoją aplikację, ale natknąłem się na przypadek, w którym deweloper przestrzegający strategii „połknięcia” stał się bardzo niewygodny. Zespół opracował duży zestaw testów i bardzo często korzystał z Thread. Teraz zaczęliśmy przeprowadzać testy na naszym serwerze CI, a czasem z powodu wad w kodzie utknęliśmy w ciągłym oczekiwaniu. Co gorsza, przy próbie anulowania zadania CI nigdy się nie zamykało, ponieważ Przerwanie Wątku, które miało przerwać test, nie przerywa zadania. Musieliśmy zalogować się do skrzynki i ręcznie zabić procesy.

Krótko mówiąc, jeśli po prostu rzucisz InterruptedException, pasujesz do domyślnej intencji, że twój wątek powinien się zakończyć. Jeśli nie możesz dodać InterruptedException do swojej listy rzutów, zawinię go w RuntimeException.

Istnieje bardzo racjonalny argument, że InterruptedException powinien być samym RuntimeException, ponieważ zachęcałoby to do lepszej „domyślnej” obsługi. To nie jest RuntimeException tylko dlatego, że projektanci trzymali się kategorycznej reguły, że RuntimeException powinien reprezentować błąd w kodzie. Ponieważ wyjątek InterruptedException nie powstaje bezpośrednio z błędu w kodzie, nie jest nim. Ale w rzeczywistości często pojawia się wyjątek InterruptedException, ponieważ w kodzie jest błąd (tj. Nieskończona pętla, dead-lock), a Interrupt jest metodą innego wątku, która radzi sobie z tym błędem.

Jeśli wiesz, że należy zrobić racjonalne porządki, zrób to. Jeśli znasz głębszą przyczynę Przerwania, możesz podjąć bardziej kompleksową obsługę.

Podsumowując, wybory do obsługi powinny być następujące:

  1. Domyślnie dodaj do rzutów.
  2. Jeśli nie możesz dodawać do rzutów, wyrzuć RuntimeException (e). (Najlepszy wybór spośród wielu złych opcji)
  3. Tylko wtedy, gdy znasz wyraźną przyczynę Przerwania, postępuj zgodnie z potrzebami. Jeśli twoja obsługa jest lokalna dla twojej metody, zresetuj przerwane przez wywołanie Thread.currentThread (). Interrupt ().
nedruod
źródło
3

Chciałem tylko dodać ostatnią opcję do tego, o czym wspomina większość ludzi i artykułów. Jak stwierdził mR_fr0g, ważne jest, aby poprawnie obsługiwać przerwanie poprzez:

  • Propagowanie wyjątku InterruptException

  • Przywróć stan przerwania w wątku

Lub dodatkowo:

  • Niestandardowa obsługa Przerwania

Nie ma nic złego w obsłudze przerwań w niestandardowy sposób, w zależności od okoliczności. Ponieważ przerwanie jest żądaniem zakończenia, w przeciwieństwie do wymuszonego polecenia, całkowicie poprawne jest wykonanie dodatkowej pracy, aby aplikacja mogła płynnie obsłużyć żądanie. Na przykład, jeśli wątek śpi, czeka na IO lub odpowiedź sprzętową, gdy odbierze przerwanie, to jest całkowicie poprawne, aby z wdziękiem zamknąć wszystkie połączenia przed zakończeniem wątku.

Bardzo polecam zrozumienie tematu, ale ten artykuł jest dobrym źródłem informacji: http://www.ibm.com/developerworks/java/library/j-jtp05236/

TheIT
źródło
0

Powiedziałbym, że w niektórych przypadkach nic nie rób. Prawdopodobnie nie jest to coś, co powinieneś robić domyślnie, ale w przypadku, gdy nie powinno być mowy o przerwaniu, nie jestem pewien, co jeszcze można zrobić (prawdopodobnie błąd logowania, ale to nie wpływa na przebieg programu).

Jeden przypadek dotyczy sytuacji, w której masz kolejkę zadań (blokujących). W przypadku, gdy masz wątek demona obsługujący te zadania i nie przerywasz wątku samodzielnie (według mojej wiedzy jvm nie przerywa wątków demona po zamknięciu jvm), nie widzę żadnego sposobu na przerwanie, dlatego może to być po prostu zignorowałem. (Wiem, że wątek demona może zostać zabity przez JVM w dowolnym momencie i dlatego w niektórych przypadkach jest nieodpowiedni).

EDYCJA: Inną sprawą mogą być strzeżone bloki, przynajmniej na podstawie samouczka Oracle pod adresem: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Bjarne Boström
źródło
To zła rada. Mam ogromne problemy z myśleniem o tak ważnym zadaniu, aby zignorować Przerwania. Oracle prawdopodobnie był po prostu leniwy i nie chciał dodawać (więcej) bałaganu do wywołania Thread.sleep (). O ile nie wiesz, dlaczego twój wątek jest przerywany, powinieneś przerwać wszystko, co robisz (powróć lub zwróć wyjątek, aby pomóc twojej gwincie umrzeć tak szybko, jak to możliwe).
Ajax
Dlatego czasami mówiłem. Również dlatego, że nie zostało to jeszcze zasugerowane i moim zdaniem w niektórych przypadkach jest całkowicie w porządku . Myślę, że to zależy od rodzaju oprogramowania, które budujesz, ale jeśli masz wątek roboczy 24/7, który nigdy nie powinien się nigdy zatrzymywać (oprócz zamykania JVM), traktowałbym przerwania np. Biorąc element z BlockingQueue jako błąd (tj. log), ponieważ „nie powinno się zdarzyć” i spróbuj ponownie. Oczywiście miałbym osobne flagi, aby sprawdzić zakończenie programu. W niektórych moich programach zamykanie JVM nie jest czymś, co powinno się zdarzyć podczas normalnej pracy.
Bjarne Boström
Lub inaczej: Moim zdaniem lepiej zaryzykować posiadanie dodatkowych instrukcji dziennika błędów (i np. Wysyłanie poczty w dziennikach błędów) niż aplikację, która powinna działać 24/7, aby zatrzymać się z powodu wyjątku przerwania, dla którego Nie widzę żadnego sposobu, by się to wydarzyło w normalnych warunkach.
Bjarne Boström
Hmm, cóż, twoje przypadki użycia mogą się różnić, ale w idealnym świecie nie kończysz się tylko dlatego, że zostałeś przerwany. Przestajesz zdejmować pracę z kolejki i pozwalasz, aby ten wątek umarł. Jeśli harmonogram wątków również zostanie przerwany i zostanie poproszony o zamknięcie, oznacza to, że JVM kończy pracę i gra się kończy. W szczególności, jeśli wyślesz kill -9 lub w inny sposób próbujesz zdjąć serwer poczty lub cokolwiek, to zignoruje cię do momentu wymuszenia wyjścia, uniemożliwiając w ten sposób inne części twojego kodu. Wymuś zabijanie podczas pisania na dysk lub bazę danych i jestem pewien, że zdarzają się cudowne rzeczy.
Ajax