Исторически сложилось так, что Java пыталась обеспечить вытесняющие ограниченные прерывания, но возникало множество проблем, таких как заброшенные Thread.stop, Thread.suspend и Thread.resume, представленные ранее. С другой стороны, учитывая надежность кода приложения Java, порог программирования снижается, и вероятность того, что программисты, не знающие основной механизм, непреднамеренно повредят систему, снижается.
Сегодня планирование потоков Java не обеспечивает вытесняющих прерываний, а использует кооперативные прерывания. На самом деле принцип кооперативного прерывания очень прост: опрос определенной отметки, указывающей на прерывание. Мы можем реализовать его в любом обычном коде.
Например, следующий код:
летучий bool isInterrupted;
//…
while(!isInterrupted) {
вычислить();
}
Однако вышеупомянутые проблемы с кодом также очевидны. Когда время выполнения вычислений относительно велико, на прерывание невозможно отреагировать вовремя. С другой стороны, используя опрос для проверки переменных флагов, невозможно прервать операции блокировки потоков, такие как ожидание и сон.
Если вы все еще используете приведенную выше идею, если вы хотите, чтобы на прерывание реагировали своевременно, вы должны проверить переменную mark с помощью планирования потоков в нижней части виртуальной машины. Да, это действительно делается в JVM.
Ниже приведен отрывок из исходного кода java.lang.Thread:
общедоступное статическое логическое значение прерывания() {
вернуть currentThread().isInterrupted(истина);
}
//…
частное собственное логическое значение isInterrupted(логическое значение ClearInterrupted);
Можно обнаружить, что isInterrupted объявлен как собственный метод, который зависит от базовой реализации JVM.
Фактически, JVM поддерживает внутренний флаг прерывания для каждого потока. Однако приложение не может напрямую получить доступ к этой переменной прерывания и должно работать с помощью следующих методов:
общественный класс Thread {
//Устанавливаем метку прерывания
общественное недействительное прерывание () { ... }
//Получаем значение метки прерывания
общедоступное логическое значение isInterrupted() { ... }
//Очищаем метку прерывания и возвращаем значение последней метки прерывания
общедоступное статическое логическое значение прерывания() { ... }
}
Обычно вызов метода прерывания потока не вызывает немедленное прерывание, а только устанавливает флаг прерывания внутри JVM. Таким образом, проверив флаг прерывания, приложение может сделать что-то особенное или полностью игнорировать прерывание.
Вы можете подумать, что если JVM предоставляет только этот грубый механизм прерываний, он по сути не имеет никаких преимуществ по сравнению с собственным методом приложения по определению переменных прерывания и опросу.
Основное преимущество внутренних переменных прерываний JVM заключается в том, что они предоставляют механизм для имитации автоматических «ловушек прерываний» в определенных ситуациях.
При выполнении блокирующих вызовов, включающих планирование потоков (например, ожидание, сон и присоединение), если происходит прерывание, заблокированный поток выдаст InterruptedException «как можно быстрее». Поэтому мы можем использовать следующую структуру кода для обработки прерываний блокировки потоков:
пытаться {
//ждём, спим или присоединяемся
}
catch(InterruptedException е) {
//Некоторая работа по обработке прерываний
}
Под «как можно быстрее» я предполагаю, что JVM проверяет переменную прерывания в промежутке между планированием потоков. Скорость зависит от реализации JVM и производительности оборудования.
Однако для некоторых операций блокировки потоков JVM не генерирует автоматически InterruptedException. Например, определенные операции ввода-вывода и операции внутренней блокировки. Для этого типа операций прерывания могут моделироваться другими способами:
1) Асинхронный ввод-вывод сокетов в java.io
При чтении и записи сокетов методы чтения и записи InputStream и OutputStream будут блокироваться и ждать, но не будут реагировать на прерывания Java. Однако после вызова метода закрытия Socket заблокированный поток выдаст исключение SocketException.
2) Асинхронный ввод-вывод реализован с помощью Selector
Если поток заблокирован в Selector.select (в java.nio.channels), вызов метода пробуждения вызовет исключение ClosedSelectorException.
3) Получение блокировки
Если поток ожидает получения внутренней блокировки, мы не можем его прервать. Однако, используя метод lockInterruptily класса Lock, мы можем предоставить возможность прерывания во время ожидания блокировки.
Кроме того, в среде, где задачи и потоки разделены, задача обычно не знает, каким потоком она будет вызвана, и, следовательно, не знает стратегии вызывающего потока по обработке прерываний. Поэтому после того, как задача установит флаг прерывания потока, нет никакой гарантии, что задача будет отменена. Таким образом, существует два принципа программирования:
1) Вы не должны прерывать поток, если не знаете его политику прерываний.
Этот принцип говорит нам, что мы не должны напрямую вызывать метод прерывания потока в такой среде, как Executer, а должны использовать такие методы, как Future.cancel, для отмены задач.
2) Код задачи не должен догадываться, что означает прерывание для потока выполнения.
Этот принцип говорит нам, что когда общий код встречает InterruptedException, он не должен его перехватывать и «проглатывать», а должен продолжать выбрасывать его в верхний код.
Короче говоря, механизм невытесняющих прерываний в Java требует от нас изменения традиционной идеи вытесняющих прерываний и принятия соответствующих принципов и шаблонов программирования, основанных на понимании его сути.