Gymterview
middle

Как остановить поток?

В Java принят кооперативный (уведомительный) механизм остановки потоков. Принудительная остановка (Thread.stop()) считается устаревшей и опасной (см. следующий вопрос).

Способ 1: Thread.interrupt()

Основной рекомендуемый механизм. Метод interrupt() выставляет флаг прерывания у потока. Поток должен самостоятельно проверять этот флаг и реагировать.

Пример: остановка через interrupt()
public class InterruptDemo implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // Полезная работа
            System.out.println("Работаю...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // sleep() сбрасывает флаг прерывания!
                // Нужно восстановить его или выйти из цикла
                System.out.println("Получен сигнал прерывания");
                Thread.currentThread().interrupt(); // Восстановить флаг
                break; // Или return
            }
        }
        System.out.println("Поток завершён корректно");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(new InterruptDemo());
        worker.start();
        Thread.sleep(3000);
        worker.interrupt(); // Уведомляем поток о необходимости остановки
    }
}

Как interrupt() взаимодействует с различными состояниями потока:

Состояние потока Что происходит при вызове interrupt()
RUNNABLE (выполняется) Устанавливается флаг прерывания; поток должен проверить его сам
TIMED_WAITING (sleep(), wait(timeout)) Поток пробуждается, выбрасывается InterruptedException, флаг сбрасывается
WAITING (wait(), join()) Аналогично TIMED_WAITING
BLOCKED (ожидание монитора) Устанавливается флаг, но поток остаётся заблокированным до получения монитора

Способ 2: собственный volatile-флаг

Альтернативный подход – объявить собственный флаг остановки:

Пример: остановка через volatile-флаг
public class VolatileFlagDemo implements Runnable {
    private volatile boolean stopped = false;

    public void stop() {
        stopped = true;
    }

    @Override
    public void run() {
        while (!stopped) {
            // Полезная работа
            System.out.println("Работаю...");
        }
        System.out.println("Поток завершён");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileFlagDemo task = new VolatileFlagDemo();
        Thread worker = new Thread(task);
        worker.start();
        Thread.sleep(3000);
        task.stop(); // Устанавливаем флаг
    }
}

Недостатки volatile-флага по сравнению с interrupt():

Аспект interrupt() volatile-флаг
Пробуждение из sleep()/wait() Да (выбрасывается InterruptedException) Нет (поток ждёт до конца)
Поддержка в стандартных API Да (все блокирующие методы реагируют) Нет (только ваш код)
Сложность реализации Проще Нужно следить за volatile

Особый случай: блокировка на I/O

Если поток заблокирован на операции ввода-вывода (InputStream.read(), Socket.accept() и т.д.), interrupt() не выведет его из этого состояния. Решения:

  • Для сетевого I/O: закрыть сокет из другого потока – метод read() выбросит SocketException.
  • Для файлового I/O: блокировка обычно кратковременная; можно дождаться завершения.
  • Для NIO-каналов (InterruptibleChannel): interrupt() закроет канал и выбросит ClosedByInterruptException.

Аналогия из жизни. interrupt() – это вежливый стук в дверь: «Можешь завершить работу?» Поток решает сам – открыть дверь сразу, доделать текущую задачу, или проигнорировать. Thread.stop() – это выбивание двери: быстро, но можно повредить мебель (данные).

На собеседовании. Покажите, что знаете именно кооперативный подход: (1) вызвать interrupt() извне, (2) проверять isInterrupted() внутри потока, (3) правильно обрабатывать InterruptedException – не глотать молча, а либо прокидывать дальше, либо восстанавливать флаг через Thread.currentThread().interrupt(). Также упомяните, что volatile-флаг – допустимая альтернатива, но interrupt() предпочтительнее, так как может прервать sleep() и wait().