Как запустить/остановить/перезапустить поток в Java?
Я столкнулся с трудностями в поиске способа запуска, остановки и перезапуска потоков в Java.
У меня есть класс Task
, который реализует интерфейс Runnable
и находится в файле Task.java
. Моему основному приложению необходимо запускать этот таск в потоке, останавливать (убивать) поток при необходимости, а также иногда убивать и перезапускать поток...
Моя первая попытка заключалась в использовании ExecutorService
, но я не могу найти способ перезапустить задачу. Когда я использую метод .shutdownNow()
, любой последующий вызов .execute()
завершается неудачей, потому что ExecutorService
находится в состоянии "шutdown"...
Как я могу решить эту проблему?
5 ответ(ов)
Когда поток останавливается, его нельзя перезапустить. Однако вы можете создать и запустить новый поток, что не запрещено.
Вариант 1: Создайте новый поток, вместо того чтобы пытаться перезапустить существующий.
Вариант 2: Вместо того, чтобы позволять потоку останавливаться, заставьте его ждать. Когда поток получит уведомление, вы можете разрешить ему продолжить работу. Таким образом, поток никогда не останавливается и не требует перезапуска.
Правка в соответствии с комментарием:
Чтобы «убить» поток, вы можете сделать что-то вроде следующего:
yourThread.setIsTerminating(true); // сообщаем потоку, что нужно остановиться
yourThread.join(); // ждем, пока поток остановится
Невозможно завершить поток, если код, выполняющийся в этом потоке, не проверяет возможность его завершения и не позволяет это сделать.
Вы сказали: "К сожалению, мне нужно его убить/перезапустить... У меня нет полного контроля над содержимым потока, и для моей ситуации требуется перезапуск."
Если содержимое потока не позволяет завершить его выполнение, то у вас нет возможности завершить этот поток.
В вашем посте вы упомянули: "Моя первая попытка заключалась в использовании ExecutorService, но я не могу найти способ перезапустить задачу. Когда я использую .shutdownNow()..."
Если вы посмотрите на исходный код метода "shutdownNow", он просто проходит по всем активно выполняющимся потокам и прерывает их. Это не остановит их выполнение, если код в этих потоках не проверяет, был ли он прерван, и не завершает выполнение самостоятельно. Поэтому shutdownNow, вероятно, не выполняет то, что вы от него ожидаете.
Позвольте объяснить, что я имею в виду, когда говорю, что содержимое потока должно позволять этому потоку быть завершенным:
myExecutor.execute(new Runnable() {
public void run() {
while (true) {
System.out.println("running");
}
}
});
myExecutor.shutdownNow();
Этот поток будет продолжать выполняться бесконечно, даже если был вызван shutdownNow, поскольку он никогда не проверяет, был ли он прерван. Однако этот поток, напротив, будет остановлен:
myExecutor.execute(new Runnable() {
public void run() {
while (!Thread.interrupted()) {
System.out.println("running");
}
}
});
myExecutor.shutdownNow();
Этот поток проверяет, был ли он прерван или нет, и поэтому завершится.
Таким образом, если вы хотите поток, который можно остановить, вам необходимо убедиться, что он проверяет прерывание. Если вы хотите поток, который можно "остановить" и "перезапустить", вы можете создать Runnable
, который сможет принимать новые задачи, как уже упоминалось ранее.
Почему нельзя остановить выполняющийся поток? На самом деле, я соврал: вы можете вызвать yourThread.stop()
, но почему это плохая идея? Поток может находиться в синхронизированном (или другом критическом) разделе кода, когда вы его останавливаете. Синхронизированные блоки должны выполняться целиком и только одним потоком, прежде чем получить доступ к ним другой поток. Если вы остановите поток посреди синхронизированного блока, защита, предоставляемая этим блоком, будет нарушена, и ваша программа перейдет в неопределенное состояние. Разработчики используют синхронизированные блоки, чтобы поддерживать вещи в синхронизации. Если вы используете threadInstance.stop()
, вы разрушаете смысл синхронизации, тот замысел, который была задумала команда разработчиков, и то, как ожидалось, что их синхронизированные блоки будут работать.
Как упомянул Тейлор Л., вы не можете просто "остановить" поток, вызвав простую функцию, поскольку это может оставить вашу систему в нестабильном состоянии — внешний вызывающий поток может не знать, что происходит внутри вашего потока.
Сказав это, самый надежный способ "остановить" поток — это позволить самому потоку следить за собой и знать, когда ему следует завершить свою работу.
Если ваша задача заключается в выполнении каких-либо действий в цикле, существует способ приостановить/перезапустить обработку, хотя, как я понимаю, это будет вне возможностей текущего API потоков (Thread API). Если это одноразовый процесс, мне не известно о каком-либо способе приостановки/перезапуска, не сталкиваясь с устаревшими или запрещенными API.
Что касается процессов с циклом, то самый простой способ, который я мог бы предложить, заключается в том, что код, создающий задачу, инстанцирует объект ReentrantLock
и передает его в задачу, а также сохраняет на него ссылку. Каждый раз, когда задача входит в свой цикл, она пытается взять блокировку на экземпляре ReentrantLock
, и когда цикл завершается, она должна разблокироваться. Вам может понадобиться обернуть это все в блок try/finally
, чтобы гарантировать, что вы освободите блокировку в конце цикла, даже если будет выброшено исключение.
Если вы хотите приостановить задачу, просто попытайтесь захватить блокировку из основного кода (поскольку у вас есть ссылка). Это заставит основной поток ждать завершения текущей итерации цикла и не позволит начать новую (поскольку основной поток удерживает блокировку). Чтобы перезапустить поток, просто разблокируйте его из основного кода, что позволит задаче возобновить свои циклы.
Для того чтобы навсегда остановить поток, я бы использовал обычный API или оставил бы флаг в задаче и метод для установки этого флага (что-то вроде stopImmediately
). Когда цикл столкнется с истинным значением для этого флага, он остановит обработку и завершит выполнение метода run
.
Ваша проблема связана с тем, что поток, который был запущен, может игнорировать прерывания, особенно если в нём есть множество вызовов Thread.sleep
, которые не учитывают исключение InterruptedException
. Это может привести к ситуации, когда одно прерывание недостаточно для завершения выполнения потока.
Предложенный вами подход с циклом, который повторно вызывает interrupt()
на потоке, пока он активен, является разумным решением. Вот улучшенный вариант вашего кода, оформленный в виде ответа на StackOverflow:
while(th.isAlive()){
log.trace("Поток всё ещё обрабатывается; отправляем прерывание;");
th.interrupt();
try {
Thread.sleep(100); // Задержка между попытками
} catch (InterruptedException e) {
// В данном случае, мы можем просто выдать стектрейс
e.printStackTrace();
// Или можно обработать прерывание текущего потока по другому
Thread.currentThread().interrupt(); // Восстанавливаем статус прерывания
}
}
Важно помнить, что если ваш поток по-прежнему обрабатывает какие-то операции и игнорирует прерывания, то использование такого подхода может быть оправдано для завершения работы. Однако всегда стоит рассмотреть возможность улучшения самого обрабатываемого кода, чтобы он корректно реагировал на прерывания и избегал зависаний.
Что значит 'synchronized'?
Разница между интерфейсами Runnable и Callable в Java
Как распределяются потоки для обработки запросов Servlet?
Следует ли использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
SwingUtilities.invokeLater: Вызов кода в потоке событий Swing