What Do You Do With InterruptedException?

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

InterruptedException является постоянным источником проблем в Java, особенно для начинающих разработчиков. Но это не должно быть так. Это довольно простая и понятная идея. Позвольте мне попробовать описать и упростить ее.

Давайте начнем с этого кода:

Что это делает? Ничего, оно просто бесконечно вращает ЦП. Можем ли мы прекратить его? Нет, в Java нет такой возможности. Оно остановится только тогда, когда вся JVM остановится, когда вы нажмете Ctrl-C. В Java нет способа прервать поток, если только поток самостоятельно не завершится. Это принцип, о котором мы должны помнить, и все остальное будет очевидно.

Давайте поместим этот бесконечный цикл в поток:

Итак, как остановить поток, когда это необходимо?

Вот как это реализовано в Java. В каждом потоке есть флаг, который мы можем установить извне. И поток может время от времени проверять его и остановить свое выполнение. Добровольно! Вот как:

Это единственный способ попросить поток остановиться. В этом примере используются два метода. Когда я вызываю loop.interrupt(), флаг устанавливается в значение true где-то внутри потока loop. Когда я вызываю interrupted(), флаг возвращается и сразу же устанавливается в значение false. Да, это дизайн метода. Он проверяет флаг, возвращает его и устанавливает в значение false. Это некрасиво, я знаю.

Таким образом, если я никогда не вызываю Thread.interrupted() внутри потока и не выхожу, когда флаг равен true, никто не сможет остановить меня. Буквально, я просто проигнорирую их вызовы interrupt(). Они будут просить меня остановиться, но я их проигнорирую. Они не смогут прервать меня.

Итак, чтобы подвести итог тому, что мы узнали до сих пор, хорошо спроектированный поток будет время от времени проверять этот флаг и остановится безопасно. Если код не проверяет флаг и никогда не вызывает Thread.interrupted(), он принимает тот факт, что рано или поздно его принудительно завершат, нажав Ctrl-C.

Пока логично? Надеюсь.

Теперь, в JDK есть несколько методов, которые проверяют этот флаг за нас и выбрасывают InterruptedException, если он установлен. Например, так спроектирован метод Thread.sleep():

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

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

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

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

Владелец потока больше не хочет ждать. И мы должны уважать решение нашего владельца.

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

Теперь еще раз посмотрите на код Thread.sleep():

Помните, что Thread.interrupted() не только возвращает флаг, но и сбрасывает его в false. Таким образом, после возникновения InterruptedException флаг сбрасывается. Поток больше не знает о запросе на прерывание, отправленном владельцем.

Владелец потока попросил нас остановиться, Thread.sleep() обнаружил этот запрос, удалил его и сгенерировал InterruptedException. Если вы снова вызовете Thread.sleep(), он не будет знать о прерывании и не сгенерирует ничего.

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

Вот что мы, в основном, делаем с InterruptedException:

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

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

Вот что нам нужно сделать:

Мы снова устанавливаем флаг в значение true!

Теперь никто не будет обвинять нас в безответственном отношении к ценному флагу. Мы нашли его в состоянии true, очистили его, установили обратно в true и вызвали исключение во время выполнения. Что произойдет дальше, нас не волнует.

Думаю, это все. Более подробное и официальное описание этой проблемы можно найти здесь: Java Theory and Practice: Dealing With InterruptedException.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-15 at 06:46

sixnines availability badge   GitHub stars