JavaRush/Курсы/Java Multithreading/Все нюансы прерывания/остановки нитей

Все нюансы прерывания/остановки нитей

Открыта

— Привет, Амиго!

Все новое – хорошо забытое старое. Сегодня я буду рассказывать про остановку нитей. Надеюсь, ты уже забыл, как работает метод interrupt().

— Да, Элли, полностью забыл.

— Отлично. Тогда напоминаю.

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

У каждой нити (у класса Thread) есть метод interrupt(), который используется для установки такого флага. При вызове метода interrupt() переменная isInterrupted внутри нити устанавливается равной true.

И когда нить вызывает методы Thread.sleep() или метод join(), в этих методах происходит скрытая проверка – а не выставлен ли у нашей текущей нити флаг isInterrupted. Если этот флаг выставлен (переменная isInterrupted равно true), то методы выбрасывают исключение InterruptedException.

Вот, напомню тебе старый пример:

Код Описание
class Clock implements Runnable
{
public void run()
{
Thread current = Thread.currentThread();

while (!current.isInterrupted())
{
Thread.sleep(1000);
System.out.println("Tik");
}
}
}
Объект Clock в своем методе run получает объект текущей его нити.

Класс Clock (часы) будет писать в консоль раз в секунду слово «Tik», пока переменная isInterrupt текущей нити равна false.

Когда переменная isInterrupt станет равной true, метод run завершится.

public static void main(String[] args)
{
Clock clock = new Clock(); Thread clockThread = new Thread(clock); clockThread.start();

Thread.sleep(10000);
clockThread.interrupt();
}
Главная нить, запускает дочернюю нить – часы, которая должна работать вечно.

Ждет 10 секунд и отменяет задание, вызовом метода interrupt.

Главная нить завершает свою работу.

Нить часов завершает свою работу.

Тут мы используем метод sleep для организации вечного цикла в методе run. В цикле есть автоматическая проверка переменной isInterrupt. Если нить вызовет метод sleep, то этот метод сначала проверит, а не установлена ли для текущей (вызвавшей его нити) переменная isInterrupt в true. И если установлена, то метод не будет спать, а выкинет исключение InterruptedException.

— Но в этом примере мы постоянно проверяем переменную isInterrupted в условии цикла.

Я помню, были какие-то причины, по которым мы не могли использовать такой подход. Не напомнишь?

— Во-первых, не всегда в методе run есть цикл. Метод может состоять просто из двух десятков вызовов других методов. Тогда перед вызовом каждого придется добавлять проверку isInterrupted.

Во-вторых, вдруг какой-то метод очень долго исполняется, т.к. делает много разных действий.

В-третьих, выкидывание исключения – это не замена проверке isInterrupted, а скорее удобное дополнение. Выкинутое исключение позволяет быстро раскрутить стек вызовов до самого run.

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

В-пятых, дополнительная проверка не приводит к снижению производительности. Вызов метода sleep значит, что нить должна ничего не делать (спать), поэтому дополнительная работа никому не мешает.

— Точно, именно это ты и говорила тогда.

А что насчет твоей фразы «Никто не гарантирует, что нить можно остановить. Она может остановиться только сама». Можешь ее растолковать?

— Конечно.

Раньше, в первых версиях Java, у нитей был метод stop(). И при его вызове Java-машина действительно останавливала нить. Но потом оказалось, что если нить, которую прерывали таким образом, делала что-то за пределами Java-машины и (например писала в файл или вызывала функции ОС), то прерывание такой нити приводила к большому количеству проблем – незакрытые файлы, неосвобожденные занятые системные ресурсы и т.д.

На общем совещании проектировщиков Java было решено убрать метод принудительной остановки у нитей. Теперь мы всего лишь можем установить определенный флаг (isInterrupted) и надеяться, что код нити был написан правильно, и этот флаг будет обработан. Этот флаг – это как плакат с надписью – «нить, остановись, пожалуйста, очень надо!». Но остановится она или нет – это ее дело.

— А как же InterruptedException?

— А если внутри кода, который работает в этой нити, есть куча try-catch блоков? Даже если InterruptedException где-нибудь да и выскочит, абсолютно не факт, что какой-то try-catch не захватит его и не забудет о нем. Так что никаких гарантий остановки нити нет.

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

— Прямо не Элли, а Шахеризада!

— Так, Амиго! Все понятно по текущей лекции?

— Ага.

— Вот и отлично.

Комментарии (89)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Максим Li Backend Developer
26 ноября 2025, 01:59
24 апреля 2025, 13:29
— Так, Амиго! Все понятно по текущей лекции? — Нет! — Вот и отлично.
SomeBody098веник в бане
25 июля 2024, 15:32
Denis Odesskiy Full Stack Developer
17 марта 2024, 02:13
Поправьте меня если я не прав, но в этой статье в классе Clock требуется обработать метод sleep() на InterruptedException, иначе код даже не скомпилируется. Ладно давайте, сделаем как в примере в лекции, но отловим InterruptedException:
public class Clock implements Runnable {

    public void run() {
        Thread current = Thread.currentThread();

        while (!current.isInterrupted()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Tik");
        }
    }
Ок, теперь все скомпилируется. И всё будет работать хорошо...или нет? Нет все будет работать плохо и неправильно. Дело в том, что мы никогда не выйдем из цикла. Все из-за того что когда будет выкинуто исключение после выполнения метода interrupt() и проверки флага в цикле, флаг будет сброшен в false и цикл уйдет в бесконечность. Так будет из-за особенности метода sleep() и InteruptException. Т.е. будет примерно это
if (Thread.interrupted())  // Clears interrupted status!
      throw new InterruptedException();
Поэтому этот флаг надо восстановить или просто прервать выполнение в блоке catch. Вот как это можно сделать: 1 вариант, восстанавливаем флаг
public void run() {
    Thread current = Thread.currentThread();
    while (!current.isInterrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); //вот тут восстанавливаем флаг
            e.printStackTrace();
        }
        System.out.println("Tik");
    }
}
Denis Odesskiy Full Stack Developer
17 марта 2024, 02:19
2 вариант, выход из цикла с помощью break или выход из метода run() с помощью return
public void run() {
    Thread current = Thread.currentThread();

    while (!current.isInterrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            break; // выходим из цикла while()
           // или
          // return; // выходим из метода run() и завершаем поток (нить)
        }
        System.out.println("Tik");
    }
}
3 вариант, обернуть try-catch цикл while(), тогда он будет прерван если возникнет исключение
try {
    while (!current.isInterrupted()) {

            Thread.sleep(1000);

        System.out.println("Tik");
    }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Поздравляю, теперь наша программа будет работать правильно и таймер часов отсчитает положенных ему 10 сек...🙂
Long_byte
Уровень 60
3 июня 2024, 16:30
а если убрать проверку и написать while(true) то нить остановится или нет?
Denis Odesskiy Full Stack Developer
3 июня 2024, 21:53
Если убрать проверку и написать просто while(true) нить будет прерываться не корректно, в данном случае будет просто ловится исключение и выход break;. Но чтобы увидеть ошибку и почему так делать не надо давайте немножечко, совсем слегка усложним пример. Допустим мы хотим не просто вызывать sleep() а еще и делать какую-то полезную работу (ну не знаю работу с I/O например (у нас просто будет пустой метод в примере)). И метод который у нас будет делать эту работу не будет кидать InterruptedException. Запустите код ниже (я его приведу просто с циклом while(true) и увидите что будет, затем поменяйте на while (!current.isInterrupted()):
package com.testall.thread;

public class Clock implements Runnable {
        public void run() {
            Thread current = Thread.currentThread();
           // Этот цикл будет бесконечным.
            while (true) {
                try {
                    Thread.sleep(1000);
                    // Другие действия, которые не бросают InterruptedException.
                    doWork();
                } catch (InterruptedException e) {
                   // Восстанавливаем флаг прерывания.
                    current.interrupt();
                    e.printStackTrace();
                }
                System.out.println("Tik");
            }
        }

        private void doWork() {
            // Выполнение какой-то работы.
        }

    public static void main(String[] args) throws InterruptedException {
        Clock clock = new Clock();
        Thread clockThread = new Thread(clock);
        clockThread.start();
        Thread.sleep(10000);
        clockThread.interrupt();
    }
}
Juki
Уровень 51
1 сентября 2023, 08:45
Все новое – хорошо забытое старое. Сегодня я буду рассказывать про остановку нитей. Надеюсь, ты уже забыл, как работает метод interrupt(). — Да, Элли, полностью забыл. наконец-то наши с Амиго ответы совпали
Fl1s Backend Developer
4 августа 2023, 23:28
кайф
Rolik
Уровень 41
17 апреля 2023, 17:43
переменная isInterrupted равно true Метод isInterrupted() принимает параметр true.
Lafaed
Уровень 36
5 апреля 2023, 18:14
— Прямо не Элли, а Шахеризада! — Так, Амиго! Все понятно по текущей лекции? — Неа. — Вот и отлично.
Lafaed
Уровень 36
5 апреля 2023, 10:13
— Надеюсь, ты уже забыл, как работает метод interrupt(). — Да, Элли, полностью забыл. У меня особый случай : не помнил, еще и забыл.
25 августа 2022, 04:37
Я помню, были какие-то причины, по которым мы не могли использовать такой подход. Не напомнишь? — Во-первых... Во-вторых... В-третьих... В-четвертых... В-пятых.. А правильно-то как делать?
Gans Electro
Уровень 36
5 июня 2023, 10:14
clockThread.interrupt(); - это правильно. while (!current.isInterrupted()){} - это нет