Class InterruptedException
Report a bug or suggest an enhancement
For further API reference and developer documentation see the Java SE Documentation, which contains more detailed, developer-targeted descriptions with conceptual overviews, definitions of terms, workarounds, and working code examples. Other versions.
Java is a trademark or registered trademark of Oracle and/or its affiliates in the US and other countries.
Copyright © 1993, 2022, Oracle and/or its affiliates, 500 Oracle Parkway, Redwood Shores, CA 94065 USA.
All rights reserved. Use is subject to license terms and the documentation redistribution policy.
The bad practice
From my own experience as a developer, the Java InterruptedException is one of those checked exceptions that you come across from time to time (much more often if you work in a concurrent programming context), but that you never deal with correctly. Generally you (or me, or the old me hopefully) either end up swallowing the exception, or at best, logging It.
Before we go into how we should deal with this typed exception, we need to understand what this exception is for in the first place.
What is the InterruptedException for?
In Java (or any other language), when we call a method, the method may take a long time to finish, either because the method itself does a lot of work, or because the method blocks on some external event (IO operations, locks…). However, in order to have responsive applications, sometimes you want to be able to cancel an operation (for example: you launch a compilation, then you try to cancel it gracefully), or, in other words, you want to be able to tell a thread to stop doing what it’s doing.
The good practice
In Java, the Thread class provides a method whose job is to interrupt a thread:
What this method does is simply set a status flag (that every thread has), the “interrupt status”. When this flag is set, It means someone is asking the thread to stop gracefully (meaning, cleaning everything it needs to clean, before stopping). When a thread notices that the flag is set, for example using the following method:
It should clear it, then throw the InterruptedException, to signal to the calling method that it was interrupted. I am using the word should here because the thread could just ignore the flag and keep doing whatever it’s doing. So the interruption is relying on good practices from the implementers of the code running in the thread.
So, when you see a method that throws an InterruptedException exception, It is basically saying that It is a blocking method, and that it provides the possibility to interrupt it gracefully, and that if it is ever interrupted, it will throw the InterruptedException exception.
Now, the mistake, that developers generally do, when they call such blocking methods, is that they don’t handle this exception correctly (for example by just catching the exception and not doing anything, the empty catch block)… What the caller of such blocking methods should do is either pass the exception up the stack, to let the caller treat it, or, if this exception can not be propagated for example (maybe because you implement an interface that you can’t change), catch the exception, do whatever cleaning you need to do on your level, and reset the interrupt status. This way, you don’t lose the information that the thread was interrupted, and you let calling methods the possibility to interrupt themselves too is case they are relying on this flag too.
InterruptedException: исключения в Java
InterruptedException — это checked exception генерируемый многими методами стандартной библиотеки, которые блокируют поток исполнения. К таким относятся: interruptible версии lock’ов, метод Thread.sleep(), некоторые операции над блокирующими очередями, некоторые операции над каналами и другие.
По сути, InterruptedException сигнализирует о том, что поток просят завершить его работу. При этом вас не просят немедленно завершить свою работу. Вас просят корректно завершить работу. На это может понадобится некоторое время. Прерывание потока осуществляется при помощи метода Thread.interrupt().
Существует два способа которыми JVM уведомляет поток о том, что его прерывают. Первый — это собственно InterruptedException. Второй — флаг потока INTERRUPT, который может быть получен при помощи метода Thread.isInterrupted(). Игнорирование второго метода сигнализирования о прерывании и является типичной ошибкой.
Если выдача InterruptedException означает, что метод является блокирующим, то вызов метода блокирования означает, что ваш метод также является блокирующим, и у вас должна быть стратегия для работы с InterruptedException. Зачастую наиболее простой стратегией является генерирование собственного InterruptedException, как показано в методах putTask() и getTask().
Выполняя это, вы также делаете ваш метод восприимчивым к прерыванию, и это для этого требуется всего-навсего добавить InterruptedException в вашу конструкцию throws.
Иногда необходимо произвести некоторую очистку, прежде чем распространить исключение. В этом случае вы можете перехватить InterruptedException, выполнить очистку, а затем повторно сгенерировать исключение.
InterruptedException позволяет прервать поток уже выполняющий блокирующий вызов. В случае, если метод уже выполняется, то существует только один способ прервать его выполнение без возврата какого-либо значения и не нарушая при этом его контракт, — сгенерировать исключительную ситуацию. В этом случае возвращаемое значение метода просто неопределено.
Объяснение InterruptedException и прерывание потоков
Если бы InterruptedException не было проверенного исключения, вероятно, никто бы даже не заметил этого — что фактически предотвратило бы пару ошибок в течение этих лет. Но поскольку с ним нужно обращаться, многие обращаются с ним неправильно или бездумно. Давайте возьмем простой пример потока, который периодически выполняет некоторую очистку, но большую часть времени находится в спящем режиме.
Этот код неверен на многих уровнях!
- Запуск Thread в конструкторе может быть плохой идеей в некоторых средах, например, некоторые среды, такие как Spring, будут создавать динамический подкласс для поддержки перехвата методов. В итоге мы получим два потока, запущенных из двух экземпляров.
- InterruptedException проглочено, а само исключение не зарегистрировано должным образом
- Этот класс запускает новый поток для каждого экземпляра, который он должен использовать ScheduledThreadPoolExecutor вместо этого, разделяемый между многими экземплярами (более надежный и эффективный для памяти)
- Кроме того, ScheduledThreadPoolExecutor мы могли бы избежать кодирования спящего / рабочего цикла самостоятельно, а также переключаться на фиксированную скорость, в отличие от представленного здесь поведения с фиксированной задержкой.
- И наконец, что не менее важно, нет способа избавиться от этой темы, даже когда на Cleaner экземпляр больше не ссылается что-либо еще.
Все проблемы действительны, но глотание
InterruptedException — это самый большой грех. Прежде чем понять причину, давайте немного подумаем, что означает это исключение и как мы можем использовать его для изящного прерывания потоков. Многие блокирующие операции в JDK объявляют метание
InterruptedException , в том числе:
- Object.wait()
- Thread.sleep()
- Process.waitFor()
- AsynchronousChannelGroup.awaitTermination()
- Различные методы блокировки в java.util.concurrent.* , например ExecutorService.awaitTermination() , Future.get() , BlockingQueue.take() , Semaphore.acquire() Condition.await() и многие, многие другие
- SwingUtilities.invokeAndWait()
Обратите внимание, что блокировка ввода / вывода не выбрасывает
InterruptedException (что является позором). Если все эти классы объявляют
InterruptedException , вы можете задаться вопросом, когда это исключение когда-либо выдается?
- Когда поток заблокирован для объявления какого-либо метода, InterruptedException и вы вызываете Thread.interrupt() такой поток, скорее всего, заблокированный метод немедленно сгенерирует InterruptedException .
- Если вы отправили задачу в пул потоков ( ExecutorService.submit() ) и вызываете ее Future.cancel(true) во время ее выполнения. В этом случае пул потоков будет пытаться прервать поток, выполняющий такую задачу для вас, эффективно прерывая вашу задачу.
Зная, что на
InterruptedException самом деле означает, мы хорошо подготовлены, чтобы справиться с этим должным образом. Если кто-то пытается прервать наш поток, и мы обнаружили его с помощью перехвата
InterruptedException , самое разумное, что нужно сделать, — позволить этому потоку завершиться, например
Обратите внимание, что
try-catch блок теперь окружает
while цикл. Таким образом, если
sleep() броски
InterruptedException , мы вырвемся из цикла. Вы можете утверждать, что мы должны записывать
InterruptedException трассировку стека. Это зависит от ситуации, так как в этом случае прерывание потока — это то, что мы действительно ожидаем, а не сбой. Но это зависит от вас. Суть в том, что если
sleep() прервется другим потоком, мы быстро уберемся от
run() всего. Если вы очень осторожны, вы можете спросить, что произойдет, если мы прервем поток, пока он находится в
cleanUp() методе, а не в спящем режиме? Часто вы сталкиваетесь с ручным флагом как это:
Однако обратите внимание, что
stop флаг (это должно быть
volatile !) Не будет прерывать операции блокировки, мы должны ждать, пока не
sleep() закончится. С другой стороны, можно утверждать, что явное
flag дает нам лучший контроль, поскольку мы можем отслеживать его значение в любое время. Оказывается, прерывание потока работает так же. Если кто-то прервал поток, когда он выполнял неблокирующие вычисления (например, внутри
cleanUp() ), такие вычисления не прерываются немедленно. Тем не менее, поток помечается как
прерванный, и каждая последующая операция блокировки (например,
sleep() ) просто сбрасывается
InterruptedException немедленно, поэтому мы не потеряем этот сигнал.
Мы также можем воспользоваться этим фактом, если напишем неблокирующий поток, который все еще хочет воспользоваться возможностью прерывания потока. Вместо того, чтобы полагаться на это,
InterruptedException мы просто должны Thread.isInterrupted() периодически проверять
:
Выше, если кто-то прерывает наш поток, мы откажемся от вычислений, как только вернемся
someHeavyComputations() . Если он будет длиться два долго или бесконечно, мы никогда не обнаружим флаг прерывания. Интересно, что
interrupted флаг не является
одноразовой площадкой . Мы можем позвонить
Thread.interrupted() вместо
isInterrupted() , который сбросит
interrupted флаг, и мы можем продолжить. Иногда вы можете игнорировать прерванный флаг и продолжать работать. В этом случае
interrupted() может пригодиться. Кстати, я (неточно) называю «добытчики», которые
меняют состояние наблюдаемого объекта, «
гейзенгеттерами ».
Обратите внимание на Thread.stop()
Если вы программист старой школы, вы можете вспомнить
Thread.stop() метод, который
устарел уже 10 лет . В Java 8 планировалось
« отменить его » , но в 1.8u5 он все еще есть. Тем не менее, не используйте его и не выполняйте рефакторинг любого кода, использующего
Thread.stop() в
Thread.interrupt() .
Uninterruptibles из гуавы
Редко вы можете игнорировать
InterruptedException вообще. В этом случае взгляните на
Uninterruptibles Гуава. У этого есть много полезных методов как
sleepUninterruptibly() или
awaitUninterruptibly(CountDownLatch) . Просто будь осторожен с ними. Я знаю, что они не объявляют
InterruptedException (что может быть горсткой), но они также полностью предотвращают прерывание текущего потока — что довольно необычно.
Резюме
К настоящему времени я надеюсь, что у вас есть некоторое понимание, почему определенные методы бросают
InterruptedException . Основные выводы: