Обработка InterruptedException в Java - PullRequest
262 голосов
/ 20 октября 2010

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

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

OR

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

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

Ответы [ 7 ]

371 голосов
/ 20 октября 2010

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

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

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

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

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

  • Если yes , тогда 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;
}

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

85 голосов
/ 20 октября 2010

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

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

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

17 голосов
/ 20 октября 2010

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

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

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

12 голосов
/ 03 августа 2011

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

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

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

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

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

8 голосов
/ 21 сентября 2013

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0 голосов
/ 25 февраля 2015

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

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

EDIT: Другой случай может быть защищенными блоками, по крайней мере, на основе учебника Oracle по адресу: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

...