0

Как запустить/остановить/перезапустить поток в Java?

8

Я столкнулся с трудностями в поиске способа запуска, остановки и перезапуска потоков в Java.

У меня есть класс Task, который реализует интерфейс Runnable и находится в файле Task.java. Моему основному приложению необходимо запускать этот таск в потоке, останавливать (убивать) поток при необходимости, а также иногда убивать и перезапускать поток...

Моя первая попытка заключалась в использовании ExecutorService, но я не могу найти способ перезапустить задачу. Когда я использую метод .shutdownNow(), любой последующий вызов .execute() завершается неудачей, потому что ExecutorService находится в состоянии "шutdown"...

Как я могу решить эту проблему?

5 ответ(ов)

0

Когда поток останавливается, его нельзя перезапустить. Однако вы можете создать и запустить новый поток, что не запрещено.

Вариант 1: Создайте новый поток, вместо того чтобы пытаться перезапустить существующий.

Вариант 2: Вместо того, чтобы позволять потоку останавливаться, заставьте его ждать. Когда поток получит уведомление, вы можете разрешить ему продолжить работу. Таким образом, поток никогда не останавливается и не требует перезапуска.

Правка в соответствии с комментарием:

Чтобы «убить» поток, вы можете сделать что-то вроде следующего:

yourThread.setIsTerminating(true); // сообщаем потоку, что нужно остановиться
yourThread.join(); // ждем, пока поток остановится
0

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

Вы сказали: "К сожалению, мне нужно его убить/перезапустить... У меня нет полного контроля над содержимым потока, и для моей ситуации требуется перезапуск."

Если содержимое потока не позволяет завершить его выполнение, то у вас нет возможности завершить этот поток.

В вашем посте вы упомянули: "Моя первая попытка заключалась в использовании 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(), вы разрушаете смысл синхронизации, тот замысел, который была задумала команда разработчиков, и то, как ожидалось, что их синхронизированные блоки будут работать.

0

Как упомянул Тейлор Л., вы не можете просто "остановить" поток, вызвав простую функцию, поскольку это может оставить вашу систему в нестабильном состоянии — внешний вызывающий поток может не знать, что происходит внутри вашего потока.

Сказав это, самый надежный способ "остановить" поток — это позволить самому потоку следить за собой и знать, когда ему следует завершить свою работу.

0

Если ваша задача заключается в выполнении каких-либо действий в цикле, существует способ приостановить/перезапустить обработку, хотя, как я понимаю, это будет вне возможностей текущего API потоков (Thread API). Если это одноразовый процесс, мне не известно о каком-либо способе приостановки/перезапуска, не сталкиваясь с устаревшими или запрещенными API.

Что касается процессов с циклом, то самый простой способ, который я мог бы предложить, заключается в том, что код, создающий задачу, инстанцирует объект ReentrantLock и передает его в задачу, а также сохраняет на него ссылку. Каждый раз, когда задача входит в свой цикл, она пытается взять блокировку на экземпляре ReentrantLock, и когда цикл завершается, она должна разблокироваться. Вам может понадобиться обернуть это все в блок try/finally, чтобы гарантировать, что вы освободите блокировку в конце цикла, даже если будет выброшено исключение.

Если вы хотите приостановить задачу, просто попытайтесь захватить блокировку из основного кода (поскольку у вас есть ссылка). Это заставит основной поток ждать завершения текущей итерации цикла и не позволит начать новую (поскольку основной поток удерживает блокировку). Чтобы перезапустить поток, просто разблокируйте его из основного кода, что позволит задаче возобновить свои циклы.

Для того чтобы навсегда остановить поток, я бы использовал обычный API или оставил бы флаг в задаче и метод для установки этого флага (что-то вроде stopImmediately). Когда цикл столкнется с истинным значением для этого флага, он остановит обработку и завершит выполнение метода run.

0

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

Предложенный вами подход с циклом, который повторно вызывает interrupt() на потоке, пока он активен, является разумным решением. Вот улучшенный вариант вашего кода, оформленный в виде ответа на StackOverflow:

while(th.isAlive()){
    log.trace("Поток всё ещё обрабатывается; отправляем прерывание;");
    th.interrupt();
    try {
        Thread.sleep(100); // Задержка между попытками
    } catch (InterruptedException e) {
        // В данном случае, мы можем просто выдать стектрейс
        e.printStackTrace();
        // Или можно обработать прерывание текущего потока по другому
        Thread.currentThread().interrupt(); // Восстанавливаем статус прерывания
    }
}

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь