1. Введение
В этом руководстве мы рассмотрим InterruptedException
в Java . Во-первых, мы быстро пройдемся по жизненному циклу потока с иллюстрацией. Далее мы увидим, как работа в многопоточных приложениях потенциально может вызвать InterruptedException
. Наконец, мы увидим, как обрабатывать это исключение.
2. Основы многопоточности
Прежде чем обсуждать прерывания, давайте рассмотрим многопоточность. Многопоточность — это процесс одновременного выполнения двух или более потоков. Приложение Java начинается с одного потока, называемого основным потоком, связанного с методом main() .
Затем этот основной поток может запускать другие потоки.
Потоки легковесны, что означает, что они выполняются в одном и том же пространстве памяти. Следовательно, они могут легко общаться между собой. Давайте посмотрим на жизненный цикл потока :
Как только мы создаем новый поток, он находится в состоянии 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 .