Перейти к основному содержимому

Как обрабатывать InterruptedException в Java

· 7 мин. чтения

Задача: Наибольшая подстрока палиндром

Для заданной строки s, верните наибольшую подстроку палиндром входящую в s. Подстрока — это непрерывная непустая последовательность символов внутри строки. Стока является палиндромом, если она читается одинаково в обоих направлениях...

ANDROMEDA 42

1. Введение

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

2. Основы многопоточности

Прежде чем обсуждать прерывания, давайте рассмотрим многопоточность. Многопоточность — это процесс одновременного выполнения двух или более потоков. Приложение Java начинается с одного потока, называемого основным потоком, связанного с методом main() . Затем этот основной поток может запускать другие потоки.

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

./a00538fc4f9fe3209ac311fc1877df38.jpg

Как только мы создаем новый поток, он находится в состоянии NEW . Он остается в этом состоянии до тех пор, пока программа не запустит поток с помощью метода start() .

Вызов метода start() для потока переводит его в состояние RUNNABLE . Потоки в этом состоянии либо запущены, либо готовы к запуску.

Когда поток ожидает блокировки монитора и пытается получить доступ к коду, заблокированному другим потоком, он переходит в состояние BLOCKED .

Поток может быть переведен в состояние WAITING различными событиями, такими как вызов метода wait() . В этом состоянии поток ожидает сигнала от другого потока.

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

В следующих разделах мы подробно рассмотрим InterruptedException и узнаем, как реагировать на него.

3. Что такое InterruptedException?

Исключение InterruptedException возникает, когда поток прерывается, когда он находится в состоянии ожидания, сна или занят другим образом. Другими словами, какой-то код вызвал метод interrupt() в нашем потоке. Это проверенное исключение , и многие блокирующие операции в Java могут вызвать его.

3.1. прерывания

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

3.2. Блокирующие и прерываемые методы

Потоки могут блокироваться по нескольким причинам: ожидание пробуждения из Thread.sleep (), ожидание получения блокировки, ожидание завершения ввода-вывода или ожидание результата вычисления в другом потоке, `` среди прочего.

InterruptedException обычно вызывается всеми методами блокировки, чтобы его можно было обработать и выполнить корректирующее действие. В Java есть несколько методов, которые вызывают InterruptedException . К ним относятся Thread.sleep() , Thread.join() , метод wait() класса Object и методы put() и take() BlockingQueue , и это лишь некоторые из них.

3.3. Методы прерывания в потоках

Давайте кратко рассмотрим некоторые ключевые методы класса Thread для обработки прерываний:

public void interrupt() { ... }
public boolean isInterrupted() { ... }
public static boolean interrupted() { ... }

Thread предоставляет метод interrupt() для прерывания потока, а чтобы узнать, был ли поток прерван, мы можем использовать метод isInterrupted () . Иногда мы можем захотеть проверить, был ли текущий поток прерван, и если да, то немедленно сгенерировать это исключение. Здесь мы можем использовать метод interrupted() :

if (Thread.interrupted()) {
throw new InterruptedException();
}

3.4. Флаг состояния прерывания

Механизм прерывания реализован с использованием флага, известного как состояние прерывания. Каждый поток имеет логическое свойство, которое представляет его прерванный статус. Вызов Thread.interrupt() устанавливает этот флаг. Когда поток проверяет наличие прерывания, вызывая статический метод Thread.interrupted() , статус прерывания очищается.

Чтобы ответить на запросы прерывания, мы должны обработать InterruptedException. Мы увидим, как это сделать, в следующем разделе.

4. Как обрабатывать InterruptedException

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

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

Существует несколько практических стратегий для обработки InterruptedException . Давайте посмотрим на них.

4.1. Распространение InterruptedException

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

public static void propagateException() throws InterruptedException {
Thread.sleep(1000);
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
throw new InterruptedException();
}
}

Здесь мы проверяем, прерван ли поток, и если да, то выбрасываем InterruptedException . Теперь давайте вызовем метод propagateException() :

public static void main(String... args) throws InterruptedException {
propagateException();
}

Когда мы попытаемся запустить этот фрагмент кода, мы получим InterruptedException с трассировкой стека:

Exception in thread "main" java.lang.InterruptedException
at com.foreach.concurrent.interrupt.InterruptExample.propagateException(InterruptExample.java:16)
at com.foreach.concurrent.interrupt.InterruptExample.main(InterruptExample.java:7)

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

4.2. Восстановить прерывание

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

Мы можем снова вызвать метод interrupt() (он установит флаг обратно в true ), чтобы код, находящийся выше в стеке вызовов, мог видеть, что было выдано прерывание. Например, давайте прервем поток и попытаемся получить доступ к его прерванному статусу:

public class InterruptExample extends Thread {
public static Boolean restoreTheState() {
InterruptExample thread1 = new InterruptExample();
thread1.start();
thread1.interrupt();
return thread1.isInterrupted();
}
}

А вот метод run() , который обрабатывает это прерывание и восстанавливает прерванное состояние:

public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //set the flag back to <code>true

} }

Наконец, давайте проверим статус:

assertTrue(InterruptExample.restoreTheState());

Хотя исключения Java охватывают все исключительные случаи и условия, мы можем захотеть создать конкретное пользовательское исключение, уникальное для кода и бизнес-логики. Здесь мы можем создать собственное исключение для обработки прерывания. Мы увидим это в следующем разделе.

4.3. Пользовательская обработка исключений

Пользовательские исключения обеспечивают гибкость добавления атрибутов и методов, которые не являются частью стандартного исключения Java. Следовательно, вполне допустимо обрабатывать прерывание по-своему, в зависимости от обстоятельств .

Мы можем выполнить дополнительную работу, чтобы приложение могло корректно обрабатывать запрос на прерывание. Например, когда поток спит или ожидает операции ввода-вывода и получает прерывание, мы можем закрыть все ресурсы перед завершением потока.

Давайте создадим пользовательское проверенное исключение с именем CustomInterruptedException :

public class CustomInterruptedException extends Exception {
CustomInterruptedException(String message) {
super(message);
}
}

Мы можем генерировать наше CustomInterruptedException, когда поток прерывается :

public static void throwCustomException() throws Exception {
Thread.sleep(1000);
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
throw new CustomInterruptedException("This thread was interrupted");
}
}

Давайте также посмотрим, как мы можем проверить, генерируется ли исключение с правильным сообщением:

@Test
public void whenThrowCustomException_thenContainsExpectedMessage() {
Exception exception = assertThrows(CustomInterruptedException.class, () -> InterruptExample.throwCustomException());
String expectedMessage = "This thread was interrupted";
String actualMessage = exception.getMessage();

assertTrue(actualMessage.contains(expectedMessage));
}

Точно так же мы можем обработать исключение и восстановить прерванный статус :

public static Boolean handleWithCustomException() throws CustomInterruptedException{
try {
Thread.sleep(1000);
Thread.currentThread().interrupt();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CustomInterruptedException("This thread was interrupted...");
}
return Thread.currentThread().isInterrupted();
}

Мы можем протестировать код, проверив статус прерывания, чтобы убедиться, что он возвращает true :

assertTrue(InterruptExample.handleWithCustomException());

5. Вывод

В этом уроке мы видели разные способы обработки InterruptedException . Если мы справимся с этим правильно, мы сможем сбалансировать отзывчивость и надежность приложения. И, как всегда, используемые здесь фрагменты кода доступны на GitHub .