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

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

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

ИЛИ

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

EDIT: Я хотел бы также знать, в каких сценариях используются эти два.

Ответ 1

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

Вы, вероятно, пришли, чтобы задать этот вопрос, потому что вы вызвали метод, который выбрасывает 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, и вы должны (должны!) Перехватить исключение. В этой ситуации важно помнить две вещи:

    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, имеет смысл сказать: "Если меня прервут, я не смогу завершить попытку чтения файла". Другими словами, имеет смысл использовать метод throw 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;
}

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

Ответ 2

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

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

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

  3. Игнорировать прерывание в методе, но восстановить состояние при выходе - e. грамм. через Гуаву Uninterruptibles. Uninterruptibles принимает код шаблона, как в примере "Невозможная задача" в JCIP § 7.1.3.

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

Ответ 6

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

  • Распространение исключения прерывания

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

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

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

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

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

Ответ 7

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

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

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