Как правильно обрабатывать InterruptedException, которое нельзя передать клиентскому коду?

Я наткнулся на ситуацию, когда мне приходится иметь дело с InterruptedException, но я не могу передать его вверх и не чувствую, что мой код должен его проглотить. Если быть точным, я работаю над реализацией распределенной блокировки, и запрос к резервной службе может быть прерван или даже истечен по времени — и, конечно, java.util.concurrent.Lock не учитывает такие случаи и не позволяет мне выплевывать InterruptedException . Я изо всех сил пытаюсь написать правильную реализацию для методов lock(), tryLock() и unlock() без выбрасывания.

Итак, вопрос в том, что было бы правильной стратегией для обработки такого случая? С текущей точки зрения я вижу только три варианта (и чувствую запах каждого из них):

  1. Игнорировать прерванное исключение в методах lock / tryLock / unlock, повторяя попытку/возвращая false/предполагая, что даже если запрос не дошел до адресата, TTL в конечном итоге разблокирует запись. Это явно не лучшее решение, потому что оно надеется на то, что все будет хорошо, а не на решение проблем.
  2. Заверните в RuntimeException наследника. Это также кажется ужасным решением, поскольку клиентскому коду придется работать с конкретной реализацией, а не с исходным интерфейсом, и непроверенное исключение, конечно, не было сделано для такой цели.
  3. Используйте вызов force Thread.currentThread().interrupt(). Мне не нравится этот способ, потому что он в основном говорит потоку обработать свое собственное прерывание, а не передавать уведомление о прерывании вызова; также, насколько я понимаю, если нет внешнего опроса, он в конце концов сделает поток, а не мгновенно обработает прерывание, возможно, совсем в другом месте.

(И, конечно, есть возможность разрешить клиентскому коду настраивать желаемое поведение, но это все равно не дает мне действительно хорошего решения)

Есть ли лучший способ, чем я описал? И если нет, то какой из них следует предпочесть другим?


person Etki    schedule 23.04.2016    source источник


Ответы (3)


Позвольте мне обсудить каждый из ваших доступных вариантов.

  1. Игнорировать прерванное исключение

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

  1. Завернуть RuntimeException

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

  1. Использовать/принудительно вызвать Thread.currentThread().interrupt()

Это может быть правильным или неправильным в зависимости от варианта использования. Спросите себя, можно ли распространять InterruptedException.

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

  • Если это не нормально, то правильный способ справиться с этим - принудительно вызвать interrupt(). Используя это, вы подавляете исключение, но по-прежнему даете клиенту возможность проверить флаг, чтобы увидеть, был ли сделан запрос на прерывание. И вы правы. Это требует, чтобы клиентская сторона опрашивала, а не обрабатывала прерывание. Но это правильно. Если вы не хотите, чтобы клиенты опрашивали, то распространение исключения было бы лучшим вариантом. Но это не всегда возможно, и ваш пример — один из таких вариантов использования. И есть много случаев, когда поток выполнения может вернуть некоторую значимую информацию, даже если он прерван. Таким образом, в этом случае исключение подавляется, но информация о том, что был запрос на завершение, все еще может быть передана выше, вызвав метод interrupt(). Таким образом, клиент может либо просто использовать результат, который был возвращен из частичного вычисления, либо опросить, чтобы проверить, был ли установлен флаг прерывания в зависимости от варианта использования. Таким образом, вы даете клиенту больше гибкости.

person MS Srikkanth    schedule 23.04.2016

Для меня ответ почти всегда 3.

В Java используется кооперативная модель прерывания: мне кажется, что это очень британский подход:

Я говорю, старина, не мог бы ты прекратить то, что ты делаешь, если это не слишком сложно?

Но нет никакого угрызения совести отреагировать на прерывание своевременно (или, действительно, вообще). Чтобы использовать цитата Робина Уильямса:

Останавливаться! ...или... я снова скажу стоп!

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

В InterruptedException нет ничего особенного — это буквально пустой подкласс Exception. Обычно он вызывается только в том случае, если конкретный метод проверяет Thread.interrupted() или .isInterrupted(). Итак, я бы не беспокоился о том, что вызов interrupt() не приводит к немедленной остановке потока - такова сама природа совместного прерывания.


Чтобы уточнить, почему я сказал «почти всегда» выше: руководство по Java описывает прерывание следующим образом:

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

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

Если бы я хотел, чтобы поток «делал что-то еще», я, вероятно, все равно использовал бы службу-исполнитель: каждое из «вещей», которые нужно сделать, представлено отдельным Runnable, поэтому я даже не знаю, выполняются ли они в все равно одна и та же ветка.

Поэтому я обычно прерывал поток, а затем бросал RuntimeException:

catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new RuntimeException(e);
}

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

По сути, это только при написании кода на уровне фреймворка (например, ExecutorService), который я бы выбрал для продолжения после прерывания.

person Andy Turner    schedule 23.04.2016
comment
Напротив, американский подход, вероятно, будет включать пистолеты :-) - person Stephen C; 23.04.2016
comment
@StephenC, и если поток не останавливается, очевидно, проблема в том, что использовалось недостаточно оружия. - person Andy Turner; 23.04.2016

Это полностью зависит от вашего приложения, в частности от значения InterruptedException в вашем потоке.

Вариант 1 для меня — плохая практика: полагаясь на другие механизмы, вы делаете свой код непонятным и страшным. Вы всегда можете поймать исключение и использовать только метод finally для освобождения всех заблокированных ресурсов.

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

person Dario Corsetti    schedule 23.04.2016