Как остановить поток?
В 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().