Обработка InterruptedException в Java

317

В чем разница между следующими способами обработки InterruptedException? Каков наилучший способ сделать это?

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

ИЛИ

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

РЕДАКТИРОВАТЬ: Я хотел бы также знать, в каких сценариях используются эти два.

devnull
источник

Ответы:

436

В чем разница между следующими способами обработки InterruptedException? Каков наилучший способ сделать это?

Вы, вероятно, пришли, чтобы задать этот вопрос, потому что вы вызвали метод, который выбрасывает InterruptedException.

Прежде всего вы должны увидеть, throws InterruptedExceptionчто это такое: часть сигнатуры метода и возможный результат вызова метода, который вы вызываете. Итак, начнем с того, что следует признать тот факт, что an InterruptedExceptionявляется вполне допустимым результатом вызова метода.

Теперь, если вызываемый вами метод выдает такое исключение, что должен делать ваш метод? Вы можете выяснить ответ, подумав о следующем:

Имеет ли смысл использовать метод, который вы реализуете InterruptedException? Иными словами, InterruptedExceptionэто разумный результат при вызове вашего метода?

  • Если да , то throws InterruptedExceptionдолжно быть частью сигнатуры вашего метода, и вы должны позволить распространению исключения (то есть вообще не перехватывать его).

    Пример . Ваш метод ожидает значения из сети, чтобы завершить вычисление и вернуть результат. Если блокирующий сетевой вызов вызывает InterruptedExceptionваш метод, вы не можете завершить вычисления обычным способом. Вы позволяете InterruptedExceptionразмножаться.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Если нет , то вы не должны объявлять свой метод с помощью, throws InterruptedExceptionи вы должны (должны!) Перехватить исключение. В этой ситуации важно помнить две вещи:

    1. Кто-то прервал вашу тему. Что кто-то, вероятно, хочет отменить операцию, завершить программу изящно или что-то еще. Вы должны быть вежливы с этим человеком и возвращаться к своему методу без лишних слов.

    2. Даже несмотря на то, что ваш метод может создать разумное возвращаемое значение в случае InterruptedException, если поток был прерван, все равно может иметь значение. В частности, код, который вызывает ваш метод, может быть заинтересован в том, произошло ли прерывание во время выполнения вашего метода. Поэтому вы должны зарегистрировать факт прерывания, установив флаг прерывания:Thread.currentThread().interrupt()

    Пример : пользователь попросил напечатать сумму двух значений. Печать " Failed to compute sum" допустима, если сумма не может быть вычислена (и намного лучше, чем позволить программе аварийно завершить работу с трассировкой стека из-за InterruptedException). Другими словами, не имеет смысла объявлять этот метод с 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");
         }
    }

К настоящему времени должно быть ясно, что просто делать throw new RuntimeException(e)это плохая идея. Это не очень вежливо к звонящему. Вы можете изобрести новое исключение во время выполнения, но основная причина (кто-то хочет, чтобы поток остановил выполнение) могла быть потеряна.

Другие примеры:

Внедрение Runnable : Как вы, возможно, обнаружили, подпись Runnable.runне допускает повторного броска InterruptedExceptions. Ну, вы подписались на внедрение Runnable, а это значит, что вы подписались, чтобы справиться с возможным InterruptedExceptions. Либо выберите другой интерфейс, например Callable, либо следуйте второму подходу выше.

 

ВызовThread.sleep : вы пытаетесь прочитать файл, и спецификация говорит, что вы должны попробовать 10 раз с интервалом в 1 секунду. Вы звоните Thread.sleep(1000). Итак, вам нужно иметь дело с InterruptedException. Для такого метода tryToReadFileимеет смысл сказать: «Если меня прервут, я не смогу завершить попытку чтения файла» . Другими словами, это имеет смысл для метода броска 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;
}

Этот пост был переписан как статья здесь .

aioobe
источник
В чем проблема со вторым методом?
блицкригз
4
В статье говорится, что вам следует позвонить, interrupt()чтобы сохранить прерванный статус. В чем причина не делать это на Thread.sleep()?
оксайт
7
Я не согласен, в том случае, если ваш поток не поддерживает прерывания, любые получаемые прерывания указывают на непредвиденное состояние (например, ошибка программирования, неправильное использование API), а выход из режима RuntimeException является подходящим ответом (fail-fast ). Если это не считается частью общего контракта потока, что, даже если вы не поддерживаете прерывания, вы должны продолжить работу, когда получите их.
amoe
12
Вызов методов, которые генерируют исключения InterruptedException и говорят, что вы «не поддерживает прерывания», выглядит как неаккуратное программирование и ожидающая ошибка. Поставить по-другому; Если вы вызываете метод, который объявлен как выбрасывающий InterruptedException, это не должно быть неожиданным условием, если этот метод фактически выдает такое исключение.
aioobe
1
@ MartiNito, нет, вызов Thread.currentThread.interrupt()в InterruptedExceptionблоке перехвата будет только устанавливать флаг прерывания. Это не заставит другого InterruptedExceptionбыть брошенным / пойманным во внешнем InterruptedExceptionблоке захвата.
aioobe
98

Как это случилось, я только что читал об этом сегодня утром по пути на работу в Java Concurrency In Practice от Брайана Гетца. В основном он говорит, что вы должны сделать одну из трех вещей

  1. РаспространитеInterruptedException - Объявите свой метод, чтобы бросить проверенный InterruptedExceptionтак, чтобы ваш вызывающий имел дело с этим.

  2. Восстановить прерывание - иногда вы не можете бросить InterruptedException. В этих случаях вы должны перехватить InterruptedExceptionи восстановить состояние прерывания, вызвав interrupt()метод, currentThreadчтобы код, находящийся выше стека вызовов, мог видеть, что было выдано прерывание, и быстро вернуться из метода. Примечание: это применимо только тогда, когда ваш метод имеет семантику «попробуй» или «наилучшее усилие», то есть ничего критического не произойдет, если метод не достигнет своей цели. Например, log()или sendMetric()может быть такой метод, или boolean tryTransferMoney(), но нет void transferMoney(). Смотрите здесь для более подробной информации.

  3. Игнорируйте прерывание в методе, но восстанавливайте статус при выходе - например, через Guava Uninterruptibles. UninterruptiblesВозьмите шаблон кода, как в примере «Невозможная задача» в JCIP § 7.1.3.
mR_fr0g
источник
10
«Иногда вы не можете выбросить InterruptedException», - я бы сказал, иногда неуместно, чтобы метод пропагандировал InterruptedException. Ваша формулировка предполагает, что вы должны выбросить InterruptedException, когда сможете .
aioobe
1
И если вы прочитаете главу 7, вы увидите, что в этом есть некоторые тонкости, подобные тем, которые приведены в листинге 7.7, где невыполнимая задача не восстанавливает прерывание сразу , а только после того, как оно выполнено. Также у Гетца есть много соавторов этой книги ...
Fizz
1
Мне нравится ваш ответ, потому что он лаконичен, однако я считаю, что он также должен мягко и быстро вернуться к вашей функции во втором случае. Представьте себе длинный (или бесконечный) цикл с Thread.sleep () в середине. Перехват InterruptedException должен вызвать Thread.currentThread (). Interrupt () и выйти из цикла.
Янн Во
@YannVo Я отредактировал ответ, чтобы применить ваше предложение.
leventov
19

Что ты пытаешься сделать?

InterruptedExceptionВыбрасывается , когда поток ожидает или спать , а другой поток прерывает его , используя interruptметод в классе Thread. Поэтому, если вы поймаете это исключение, это означает, что поток был прерван. Обычно нет смысла Thread.currentThread().interrupt();снова звонить , если только вы не хотите проверить «прерванный» статус потока откуда-то еще.

Что касается вашего другого варианта броска RuntimeException, это не кажется очень мудрым делом (кто поймает это? Как это будет обработано?), Но трудно сказать больше без дополнительной информации.

Grodriguez
источник
14
Вызов Thread.currentThread().interrupt()устанавливает флаг прерывания (снова), что действительно полезно, если мы хотим, чтобы прерывание было замечено и обработано на более высоком уровне.
Петер Тёрёк
1
@ Петер: Я только обновлял ответ, чтобы упомянуть об этом. Спасибо.
Гродригес
12

Для меня главное в этом: InterruptedException - это не ошибка, это поток, выполняющий то, что вы ему сказали. Поэтому перебрасывание его в оболочку RuntimeException не имеет смысла.

Во многих случаях имеет смысл перебрасывать исключение, заключенное в исключение RuntimeException, когда вы говорите: я не знаю, что здесь пошло не так, и не могу ничего сделать, чтобы это исправить, я просто хочу, чтобы оно вышло из текущего потока обработки и ударил любой обработчик исключений для всего приложения, который у меня есть, чтобы он мог его записать. Это не относится к InterruptedException, это просто поток, отвечающий на вызов interrupt () для него, он выдает InterruptedException, чтобы помочь своевременно отменить обработку потока.

Поэтому распространяйте InterruptedException или используйте его разумно (имея в виду место, где он должен был выполнить то, что предполагалось) и сбросьте флаг прерывания. Обратите внимание, что флаг прерывания очищается при возникновении исключения InterruptedException; разработчики библиотеки Jdk предполагают, что перехват исключения сводится к его обработке, поэтому по умолчанию флаг очищается.

Так что, безусловно, первый способ лучше, второй опубликованный пример в вопросе бесполезен, если только вы не ожидаете, что поток действительно прервется, и прерывание приведет к ошибке.

Вот ответ, который я написал, описывающий, как работают прерывания, с примером . Вы можете увидеть в примере кода, где он использует InterruptedException для выхода из цикла while в методе run Runnable.

Натан Хьюз
источник
10

Правильный выбор по умолчанию - добавить InterruptedException в ваш список бросков. Прерывание указывает, что другой поток желает, чтобы ваш поток завершился. Причина этого запроса не очевидна и носит полностью контекстуальный характер, поэтому, если у вас нет дополнительных знаний, вы должны предполагать, что это просто дружественное завершение работы, а все, что позволяет избежать такого выключения, является неприемлемым ответом.

Java не будет случайным образом выбрасывать InterruptedException, все советы не будут влиять на ваше приложение, но я столкнулся со случаем, когда следование стратегии «ласточки» разработчика стало очень неудобным. Команда разработала большой набор тестов и много использовала Thread.Sleep. Теперь мы начали запускать тесты на нашем CI-сервере, и иногда из-за дефектов в коде застревали в постоянном ожидании. Что еще хуже, при попытке отменить задание CI оно никогда не закрывалось, поскольку прерывание Thread.Interrupt, предназначенное для прерывания теста, не прерывало задание. Нам пришлось войти в ящик и вручную убить процессы.

Короче говоря, если вы просто выбросите InterruptedException, вы соответствуете намерению по умолчанию, что ваш поток должен завершиться. Если вы не можете добавить InterruptedException в свой список бросков, я поместил бы его в RuntimeException.

Существует очень рациональный аргумент в пользу того, что InterruptedException должен быть самим RuntimeException, поскольку это будет способствовать лучшей обработке по умолчанию. Это не RuntimeException только потому, что разработчики придерживались категориального правила, согласно которому RuntimeException должно представлять ошибку в вашем коде. Поскольку InterruptedException не возникает непосредственно из-за ошибки в вашем коде, это не так. Но реальность такова, что часто возникает InterruptedException, потому что в вашем коде есть ошибка (т. Е. Бесконечный цикл, тупик), а Interrupt - это метод какого-то другого потока для обработки этой ошибки.

Если вы знаете, что есть рациональная очистка, то сделайте это. Если вы знаете более глубокую причину прерывания, вы можете взять на себя более полную обработку.

Итак, в итоге ваш выбор для обработки должен следовать этому списку:

  1. По умолчанию добавьте к броскам.
  2. Если не разрешено добавлять к броскам, бросить RuntimeException (e). (Лучший выбор из нескольких плохих вариантов)
  3. Только когда вы знаете явную причину прерывания, обрабатывайте его как хотите. Если ваша обработка является локальной для вашего метода, тогда сброс прерывается прерванным вызовом Thread.currentThread (). Interrupt ().
nedruod
источник
3

Я просто хотел добавить одну последнюю опцию к тому, что упоминают большинство людей и статьи. Как заявил mR_fr0g, важно правильно обрабатывать прерывание одним из следующих способов:

  • Распространение InterruptException

  • Восстановить состояние прерывания в потоке

Или дополнительно:

  • Пользовательская обработка прерываний

Нет ничего плохого в обработке прерывания в соответствии с вашими обстоятельствами. Поскольку прерывание является запросом на завершение, в отличие от принудительной команды, вполне допустимо завершить дополнительную работу, чтобы приложение могло корректно обработать запрос. Например, если поток находится в режиме ожидания, ожидает ввода-вывода или аппаратного ответа, когда он получает прерывание, то вполне корректно корректно закрыть все соединения перед завершением потока.

Я настоятельно рекомендую разобраться в теме, но эта статья является хорошим источником информации: http://www.ibm.com/developerworks/java/library/j-jtp05236/

Это
источник
0

Я бы сказал, что в некоторых случаях ничего не поделаешь. Возможно, это не то, что вы должны делать по умолчанию, но в случае, если не должно быть никакого способа для прерывания, я не уверен, что еще делать (возможно, ошибка регистрации, но это не влияет на ход программы).

Один случай - случай, когда у вас есть очередь задач (блокировка). Если у вас есть Поток демона, выполняющий эти задачи, и вы не прерываете Поток самостоятельно (насколько мне известно, jvm не прерывает потоки демона при завершении работы jvm), я не вижу возможности для прерывания, и поэтому это может быть просто игнорируется. (Я знаю, что поток демонов может быть убит jvm в любое время и, следовательно, в некоторых случаях не подходит).

РЕДАКТИРОВАТЬ: другой случай может быть защищенных блоков, по крайней мере, на основе учебника Oracle по адресу: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Бьярне Бострем
источник
Это плохой совет. Мне крайне трудно думать о любой задаче, которая так важна, чтобы игнорировать прерывания. Oracle, вероятно, просто ленив и не хотел добавлять (больше) беспорядка в вызов Thread.sleep (). Если вы не знаете, почему ваш поток прерывается, вам следует прекратить все, что вы делаете (либо вернуть, либо сбросить исключение, чтобы помочь вашему потоку умереть как можно быстрее).
Аякс
Вот почему я иногда говорил. Кроме того, потому что это еще не было предложено, и в некоторых случаях, на мой взгляд, это совершенно нормально . Я предполагаю, что это зависит от того, какое программное обеспечение вы создаете, но если у вас есть рабочий поток 24/7, который никогда не должен останавливаться (кроме выключения JVM), я бы воспринимал прерывания, как, например, получение элемента из BlockingQueue как ошибку (т.е. журнал), так как "это не должно произойти" и попробуйте снова. Конечно, у меня были бы отдельные флаги для проверки завершения программы. В некоторых из моих программ выключение JVM - это не то, что должно происходить при нормальной работе.
Бьярне Бострем
Или, говоря по-другому: на мой взгляд, лучше рискнуть иметь дополнительные операторы журнала ошибок (и, например, отправлять почту в журналах ошибок), чем иметь приложение, которое должно работать 24/7 для остановки из-за прерывания, для которого Я не вижу, как это происходит в нормальных условиях.
Бьярне Бострем,
Хм, ну, ваши варианты использования могут отличаться, но в идеальном мире вы не просто завершаете работу, потому что вас прервали. Вы перестаете снимать работу с очереди и позволяете ЭТОМУ потоку умереть. Если планировщик потоков также прерывается и сообщает о завершении работы, JVM завершается, и его игра заканчивается. В частности, если вы отправите команду kill -9 или иным образом попытаетесь отключить ваш почтовый сервер или что-то еще, то он будет игнорировать вас до тех пор, пока вы не принудительно завершите работу, тем самым предотвратив постепенное отключение других частей вашего кода. Принудительно убивайте во время записи на диск или в БД, и я уверен, что произойдут замечательные вещи.
Аякс