Interrupted Exception
Недавно еще раз встретился с некорректной обработкой InterruptedException в java. InterruptedException — это checked exception генерируемый многими методами стандартной библиотеки, которые блокируют поток исполнения. К таким относятся: interruptible версии lock’ов, метод Thread.sleep(), некоторые операции над блокирующими очередями, некоторые операции над каналами и другие.
По своей сути, InterruptedException сигнализирует о том, что поток просят завершить его работу. При этом вас не просят немедленно завершить свою работу. Вас просят корректно завершить работу. На это может понадобится некоторое время.
Прерывание потока осуществляется при помощи метода Thread.interrupt(). Существует два способа которыми JVM уведомляет поток о том, что его прерывают. Первый — это собственно InterruptedException . Второй — это флаг потока INTERRUPT , который может быть получен при помощи метода Thread.isInterrupted() . Игнорирование второго метода сигнализирования о прерывании и является типичной ошибкой.
Важно понимать, что interrupt адресуется не только вам (коду, который выполняется в потоке), но и владельцу потока. Другими словами, не только вы в этом потоке заинтересованы в том, чтобы определить что наступило прерывание.
try Object o = queue.take(); > catch ( InterruptedException e ) <>
Данный код неверен, потому что он “давит” сигнал прерывания. Если этот код выполняется в thread pool’е, то на этом таске worker (который вызвал вас) должен был бы завершить исполнение задач, так как поток получил прерывание. Но этого не произойдет потому что сигнал прерывания был перехвачен вышеприведенным кодом. Как правило, это выражается в том, что после посылки kill сигнала java машине у нее остаются висеть потоки worker’ов из-за чего JVM не может завершить свою работу. В данном случае единственный выход — kill -9 .
Следующий код так же некорректен, потому что мы “маскируем” interrupt в исключительную ситуацию другого типа. Тем самым он не дает возможности вызывающей стороне зафиксировать ситуацию прерывания.
try Object o = queue.take(); > catch ( InterruptedException e ) throw new RuntimeException(e); >
Корректный код будет выглядеть следующим образом.
try Object o = queue.take(); > catch ( InterruptedException e ) Thread.currentThread().interrupt(); >
Здесь мы ловим InterruptedException и выставляем флаг потока сигнализирующий о прерывании.
Политика прерывания метода является частью его контракта. Если вы декларируете что InterruptedException не может произойти во время выполнения метода, но сами при этом вызываете методы генерирующие эту исключительную ситуацию, то вы должны корректно конвертировать InterruptedException во флаг потока о прерывании. То же самое относится и к вызывающей стороне. Если вы вызываете метод, который по контракту генерирует InterruptedException , то вы должны ожидать что метод будет уведомлять вас о прерывании посредством исключительной ситуации. Если нет, то посредством флага.
Внимательный читатель мог задать себе вопрос: “А зачем два способа сигнализации? Разве нельзя обойтись одним?”.
Зачем нужен Thread.isInterrupted() ? Link to heading
InterruptedException бывает неудобен по причине того, что это checked exception. Это значит что компилятор заставляет вас или обработать его по месту, или указать в своем контракте. В случае, если вы не можете указать InterruptedException в своем контракте (например, вы имплементируете интерфейс где в контракте InterruptedException не указан), флаг потока остается единственным способом передать вызывающей стороне информацию о прерывании.
Зачем нужен InterruptedException ? Link to heading
InterruptedException позволяет прервать поток уже выполняющий блокирующий вызов. В случае, если метод уже выполняется, то существует только один способ прервать его выполнение без возврата какого-либо значения и не нарушая при этом его контракт, — сгенерировать исключительную ситуацию. В этом случае возвращаемое значение метода просто неопределено.
InterruptedException: исключения в Java
InterruptedException — это checked exception генерируемый многими методами стандартной библиотеки, которые блокируют поток исполнения. К таким относятся: interruptible версии lock’ов, метод Thread.sleep(), некоторые операции над блокирующими очередями, некоторые операции над каналами и другие.
По сути, InterruptedException сигнализирует о том, что поток просят завершить его работу. При этом вас не просят немедленно завершить свою работу. Вас просят корректно завершить работу. На это может понадобится некоторое время. Прерывание потока осуществляется при помощи метода Thread.interrupt().
Существует два способа которыми JVM уведомляет поток о том, что его прерывают. Первый — это собственно InterruptedException. Второй — флаг потока INTERRUPT, который может быть получен при помощи метода Thread.isInterrupted(). Игнорирование второго метода сигнализирования о прерывании и является типичной ошибкой.
Если выдача InterruptedException означает, что метод является блокирующим, то вызов метода блокирования означает, что ваш метод также является блокирующим, и у вас должна быть стратегия для работы с InterruptedException. Зачастую наиболее простой стратегией является генерирование собственного InterruptedException, как показано в методах putTask() и getTask().
public class TaskQueue < private static final int MAX_TASKS = 1000; private BlockingQueuequeue = new LinkedBlockingQueue(MAX_TASKS); public void putTask(Task r) throws InterruptedException < queue.put(r); >public Task getTask() throws InterruptedException < return queue.take(); >>
Выполняя это, вы также делаете ваш метод восприимчивым к прерыванию, и это для этого требуется всего-навсего добавить InterruptedException в вашу конструкцию throws.
Иногда необходимо произвести некоторую очистку, прежде чем распространить исключение. В этом случае вы можете перехватить InterruptedException, выполнить очистку, а затем повторно сгенерировать исключение.
InterruptedException позволяет прервать поток уже выполняющий блокирующий вызов. В случае, если метод уже выполняется, то существует только один способ прервать его выполнение без возврата какого-либо значения и не нарушая при этом его контракт, — сгенерировать исключительную ситуацию. В этом случае возвращаемое значение метода просто неопределено.
Interruptedexception java что это
Примеры потоков ранее представляли поток как последовательный набор операций. После выполнения последней операции завершался и поток. Однако нередко имеет место и другая организация потока в виде бесконечного цикла. Например, поток сервера в бесконечном цикле прослушивает определенный порт на предмет получения данных. И в этом случае мы также можем предусмотреть механизм завершения потока.
Завершение потока
Распространенный способ завершения потока представляет опрос логической переменной. И если она равна, например, false, то поток завершает бесконечный цикл и заканчивает свое выполнение.
Определим следующий класс потока:
class MyThread implements Runnable < private boolean isActive; void disable()< isActive=false; >MyThread() < isActive = true; >public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(isActive)< System.out.println("Loop " + counter++); try< Thread.sleep(400); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >> System.out.printf("%s finished. \n", Thread.currentThread().getName()); > >
Переменная isActive указывает на активность потока. С помощью метода disable() мы можем сбросить состояние этой переменной.
Теперь используем этот класс:
public static void main(String[] args) < System.out.println("Main thread started. "); MyThread myThread = new MyThread(); new Thread(myThread,"MyThread").start(); try< Thread.sleep(1100); myThread.disable(); Thread.sleep(1000); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); >
Итак, вначале запускается дочерний поток: new Thread(myThread,»MyThread»).start() . Затем на 1100 миллисекунд останавливаем Main thread и потом вызываем метод myThread.disable() , который переключает в потоке флаг isActive. И дочерний поток завершается.
Метод interrupt
Еще один способ вызова завершения или прерывания потока представляет метод interrupt() . Вызов этого метода устанавливает у потока статус, что он прерван. Сам метод возвращает true, если поток может быть прерван, в ином случае возвращается false.
При этом сам вызов этого метода НЕ завершает поток, он только устанавливает статус: в частности, метод isInterrupted() класса Thread будет возвращать значение true. Мы можем проверить значение возвращаемое данным методом и прозвести некоторые действия. Например:
class JThread extends Thread < JThread(String name)< super(name); >public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!isInterrupted())< System.out.println("Loop " + counter++); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); JThread t = new JThread("JThread"); t.start(); try< Thread.sleep(150); t.interrupt(); Thread.sleep(150); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); > >
В классе, который унаследован от Thread, мы можем получить статус текущего потока с помощью метода isInterrupted() . И пока этот метод возвращает false, мы можем выполнять цикл. А после того, как будет вызван метод interrupt, isInterrupted() возвратит true, и соответственно произойдет выход из цикла.
Возможный консольный вывод:
Main thread started. JThread started. Loop 1 Loop 2 Loop 3 Loop 4 JThread finished. Main thread finished.
Если основная функциональность заключена в классе, который реализует интерфейс Runnable, то там можно проверять статус потока с помощью метода Thread.currentThread().isInterrupted() :
class MyThread implements Runnable < public void run()< System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!Thread.currentThread().isInterrupted())< System.out.println("Loop " + counter++); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); MyThread myThread = new MyThread(); Thread t = new Thread(myThread,"MyThread"); t.start(); try< Thread.sleep(150); t.interrupt(); Thread.sleep(150); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); > >
Однако при получении статуса потока с помощью метода isInterrupted() следует учитывать, что если мы обрабатываем в цикле исключение InterruptedException в блоке catch, то при перехвате исключения статус потока автоматически сбрасывается, и после этого isInterrupted будет возвращать false.
Например, добавим в цикл потока задержку с помощью метода sleep:
public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!isInterrupted())< System.out.println("Loop " + counter++); try< Thread.sleep(100); >catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); System.out.println(isInterrupted()); // false interrupt(); // повторно сбрасываем состояние >> System.out.printf("%s finished. \n", Thread.currentThread().getName()); >
Когда поток вызовет метод interrupt, метод sleep сгенерирует исключение InterruptedException, и управление перейдет к блоку catch. Но если мы проверим статус потока, то увидим, что метод isInterrupted возвращает false. Как вариант, в этом случае мы можем повторно прервать текущий поток, опять же вызвав метод interrupt(). Тогда при новой итерации цикла while метода isInterrupted возвратит true, и поизойдет выход из цикла.
Либо мы можем сразу же в блоке catch выйти из цикла с помощью break:
while(!isInterrupted()) < System.out.println("Loop " + counter++); try< Thread.sleep(100); >catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); break; // выход из цикла >>
Если бесконечный цикл помещен в конструкцию try. catch, то достаточно обработать InterruptedException:
public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов try< while(!isInterrupted())< System.out.println("Loop " + counter++); Thread.sleep(100); >> catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); >
Обработка InterruptedException в Java
Одной из типичных задач в програмировании на Java является работа с потоками и обработка исключений, которые могут возникнуть при их использовании.
Алексей Кодов
Автор статьи
10 июля 2023 в 15:35
Одной из типичных задач в програмировании на Java является работа с потоками и обработка исключений, которые могут возникнуть при их использовании. Например, весьма распространённой является ситуация, когда один поток прерывается другим потоком, и в этом случае возникает исключение типа InterruptedException .
try < // Код, который может вызвать InterruptedException >catch (InterruptedException e) < // Обработка исключения >
Существуют различные способы обработки этого исключения, и выбор конкретного метода зависит от конкретной ситуации и требуемого поведения программы. Рассмотрим два наиболее распространённых подхода.
Первый способ: установка флага прерывания
try < // Код, который может вызвать InterruptedException >catch (InterruptedException e)
В этом случае, когда происходит InterruptedException , устанавливается флаг прерывания текущего потока. Этот подход позволяет передать информацию о прерывании выше по стеку вызовов, что полезно, если текущий метод не знает, как правильно обработать прерывание.
Второй способ: проброс RuntimeException
try < // Код, который может вызвать InterruptedException >catch (InterruptedException e)
В этом случае при возникновении InterruptedException выбрасывается непроверяемое исключение RuntimeException . Этот подход применяется, когда обработка InterruptedException в текущем контексте невозможна или нежелательна, и требуется прервать выполнение текущего блока кода.
Выбор между этими двумя способами зависит от конкретной задачи. Первый подход обычно используется, когда есть вероятность, что прерывание потока будет корректно обработано на более высоком уровне. Второй подход применяется, когда нет уверенности, что прерывание будет корректно обработано, и лучше прервать выполнение кода, выбросив исключение.