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

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

Java Multithreading
5 уровень , 6 лекция
Открыта

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

Все новое – хорошо забытое старое. Сегодня я буду рассказывать про остановку нитей. Надеюсь, ты уже забыл, как работает метод 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 Уровень 21
26 ноября 2025
24 апреля 2025
— Так, Амиго! Все понятно по текущей лекции? — Нет! — Вот и отлично.
SomeBody098 Уровень 51
25 июля 2024
Denis Odesskiy Уровень 47
17 марта 2024
Поправьте меня если я не прав, но в этой статье в классе 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 Уровень 47
17 марта 2024
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 Уровень 57
3 июня 2024
а если убрать проверку и написать while(true) то нить остановится или нет?
Denis Odesskiy Уровень 47
3 июня 2024
Если убрать проверку и написать просто 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
Все новое – хорошо забытое старое. Сегодня я буду рассказывать про остановку нитей. Надеюсь, ты уже забыл, как работает метод interrupt(). — Да, Элли, полностью забыл. наконец-то наши с Амиго ответы совпали
Fl1s Уровень 51
4 августа 2023
кайф
Rolik Уровень 41
17 апреля 2023
переменная isInterrupted равно true Метод isInterrupted() принимает параметр true.
Lafaed Уровень 36
5 апреля 2023
— Прямо не Элли, а Шахеризада! — Так, Амиго! Все понятно по текущей лекции? — Неа. — Вот и отлично.
Lafaed Уровень 36
5 апреля 2023
— Надеюсь, ты уже забыл, как работает метод interrupt(). — Да, Элли, полностью забыл. У меня особый случай : не помнил, еще и забыл.
Сергей Смирнов Уровень 36
25 августа 2022
Я помню, были какие-то причины, по которым мы не могли использовать такой подход. Не напомнишь? — Во-первых... Во-вторых... В-третьих... В-четвертых... В-пятых.. А правильно-то как делать?
Gans Electro Уровень 4
5 июня 2023
clockThread.interrupt(); - это правильно. while (!current.isInterrupted()){} - это нет