Обработка InterruptedException в Java
В чем разница между следующими способами обработки InterruptedException
? Каков наилучший способ сделать это?
try{
//...
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
ИЛИ ЖЕ
try{
//...
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
РЕДАКТИРОВАТЬ: Я хотел бы также знать, в каких сценариях используются эти два.
7 ответов
Вы, вероятно, пришли, чтобы задать этот вопрос, потому что вы вызвали метод, который бросает InterruptedException
,
Прежде всего, вы должны увидеть throws InterruptedException
для чего это: Часть сигнатуры метода и возможный результат вызова метода, который вы вызываете. Итак, начните с того, что 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
и вы должны (должны!) поймать исключение. В этой ситуации важно помнить две вещи:Кто-то прервал вашу тему. Что кто-то, вероятно, хочет отменить операцию, завершить программу изящно или что-то еще. Вы должны быть вежливы с этим человеком и возвращаться к своему методу без лишних слов.
Даже если ваш метод может произвести разумное возвращаемое значение в случае
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; }
Этот пост был переписан как статья здесь.
Как это случилось, я только что прочитал об этом сегодня утром по пути на работу в Java Concurrency In Practice, Брайана Гетца. В основном он говорит, что вы должны сделать одну из двух вещей
Распространять
InterruptedException
- Объявите свой метод, чтобы бросить проверенныйInterruptedException
так что ваш абонент должен иметь дело с этим.Восстановить прерывание - иногда вы не можете бросить
InterruptedException
, В этих случаях вы должны пойматьInterruptedException
и восстановить статус прерывания, вызвавinterrupt()
метод наcurrentThread
поэтому код, находящийся выше стека вызовов, может видеть, что было выдано прерывание.
Что ты пытаешься сделать?
InterruptedException
генерируется, когда поток ожидает или спит, а другой поток прерывает его, используя interrupt
метод в классе Thread
, Так что если вы поймаете это исключение, это означает, что поток был прерван. Обычно нет смысла звонить Thread.currentThread().interrupt();
опять же, если только вы не хотите проверить "прерванное" состояние потока откуда-то еще.
Что касается вашего другого варианта броска RuntimeException
, это не кажется очень мудрым делом (кто поймает это? как это будет обработано?), но трудно сказать больше без дополнительной информации.
Правильный выбор по умолчанию - добавить InterruptedException в ваш список бросков. Прерывание указывает, что другой поток желает, чтобы ваш поток завершился. Причина этого запроса не очевидна и носит полностью контекстуальный характер, поэтому, если у вас нет никаких дополнительных знаний, вы должны предполагать, что это просто дружественное завершение работы, а все, что позволяет избежать такого выключения, является неприемлемым ответом.
Java не будет случайным образом выбрасывать InterruptedException, все советы не будут влиять на ваше приложение, но я столкнулся со случаем, когда следование стратегии "ласточки" разработчика стало очень неудобным. Команда разработала большой набор тестов и много использовала Thread.Sleep. Теперь мы начали запускать тесты на нашем CI-сервере, и иногда из-за дефектов в коде застревали в постоянном ожидании. Что еще хуже, при попытке отменить задание CI оно никогда не закрывалось, поскольку прерывание Thread.Interrupt, предназначенное для прерывания теста, не прерывало задание. Пришлось войти в ящик и вручную убить процессы.
Короче говоря, если вы просто выбросите InterruptedException, вы соответствуете намерению по умолчанию, что ваш поток должен закончиться. Если вы не можете добавить InterruptedException в свой список бросков, я поместил бы его в RuntimeException.
Существует очень рациональный аргумент в пользу того, что InterruptedException должен быть самим RuntimeException, поскольку это будет способствовать лучшей обработке по умолчанию. Это не RuntimeException только потому, что разработчики придерживались категориального правила, согласно которому RuntimeException должно представлять ошибку в вашем коде. Поскольку InterruptedException не возникает непосредственно из-за ошибки в вашем коде, это не так. Но реальность такова, что часто возникает InterruptedException, потому что в вашем коде есть ошибка (т. Е. Бесконечный цикл, тупик), а Interrupt - это метод какого-то другого потока для работы с этой ошибкой.
Если вы знаете, что есть рациональная очистка, то сделайте это. Если вы знаете более глубокую причину прерывания, вы можете взять на себя более полную обработку.
Таким образом, в итоге ваш выбор для обработки должен следовать этому списку:
- По умолчанию добавьте к броскам.
- Если не разрешено добавлять к броскам, бросить RuntimeException (e). (Лучший выбор из нескольких плохих вариантов)
- Только когда вы знаете явную причину прерывания, обрабатывайте как хотите. Если ваша обработка является локальной для вашего метода, тогда сброс прерывается прерванным вызовом Thread.currentThread(). Interrupt().
Для меня главное в этом: InterruptedException - это не ошибка, это поток, выполняющий то, что вы ему сказали. Поэтому перебрасывание его в оболочку RuntimeException не имеет смысла.
Во многих случаях имеет смысл перебрасывать исключение, заключенное в RuntimeException, когда вы говорите, что я не знаю, что здесь пошло не так, и я не могу ничего сделать, чтобы это исправить, я просто хочу, чтобы оно вышло из текущего потока обработки и ударил любой обработчик исключений для всего приложения, который у меня есть, чтобы он мог регистрировать его Это не относится к InterruptedException, это просто поток, отвечающий на вызов interrupt() для него, он выдает InterruptedException, чтобы помочь своевременно отменить обработку потока.
Поэтому распространяйте InterruptedException или используйте его разумно (имея в виду место, где он должен был выполнить то, что должен был сделать) и сбросьте флаг прерывания. Обратите внимание, что флаг прерывания сбрасывается при возникновении исключения InterruptedException; разработчики библиотеки Jdk предполагают, что перехват исключения сводится к его обработке, поэтому по умолчанию флаг очищается.
Так что, безусловно, первый способ лучше, второй опубликованный пример в вопросе бесполезен, если только вы не ожидаете, что поток действительно прервется, и прерывание приведет к ошибке.
Вот ответ, который я написал, описывающий, как работают прерывания, с примером. Вы можете видеть в примере кода, где он использует InterruptedException для выхода из цикла while в методе run Runnable.
Я просто хотел добавить одну последнюю опцию к тому, что упоминают большинство людей и статьи. Как заявил mR_fr0g, важно правильно обрабатывать прерывание одним из следующих способов:
Распространение InterruptException
Восстановить состояние прерывания в потоке
Или дополнительно:
- Пользовательская обработка прерываний
Нет ничего плохого в том, чтобы обрабатывать прерывания в зависимости от ваших обстоятельств. Поскольку прерывание является запросом на завершение, в отличие от принудительной команды, вполне допустимо выполнить дополнительную работу, чтобы приложение могло корректно обработать запрос. Например, если поток находится в режиме ожидания, ожидает ввода-вывода или аппаратного ответа, когда он получает прерывание, то вполне корректно корректно закрыть все соединения перед завершением потока.
Я настоятельно рекомендую разобраться в теме, но эта статья является хорошим источником информации: http://www.ibm.com/developerworks/java/library/j-jtp05236/
Я бы сказал, что в некоторых случаях ничего не поделаешь. Вероятно, это не то, что вы должны делать по умолчанию, но в случае, если не должно быть способа для прерывания, я не уверен, что еще делать (возможно, ошибка регистрации, но это не влияет на ход программы).
Один случай - случай, когда у вас есть очередь задач (блокировка). В случае, если у вас есть Поток демона, выполняющий эти задачи, и вы не прерываете Поток самостоятельно (насколько мне известно, jvm не прерывает потоки демона при завершении работы jvm), я не вижу возможности для прерывания, и поэтому это может быть просто игнорируется. (Я знаю, что поток демонов может быть прерван jvm в любое время и, следовательно, в некоторых случаях непригоден).
РЕДАКТИРОВАТЬ: другой случай может быть защищенных блоков, по крайней мере, на основе учебника Oracle по адресу: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html