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

Ищу ответ на вопрос как остановить поток в многопоточной системе, если код, в цикле (с флагом прерывания) завис

public class Processor implements Runnable {
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) 
                break;
            
             while (true) {//чужой код, периодически виснет, доступа к нему нет
                 System.out.println("Stop My!!!!!");//чужой код, периодически виснет, доступа к нему нет
             }//чужой код, периодически виснет, доступа к нему нет
        }
    }
}

Как остановить поток в таком случае, если у потока нет возможности опроса флага?


Ответы (3 шт):

Автор решения: DedMoroz

Поток невозможно прервать принудительно — он может завершиться только по собственной инициативе.

Однако существует механизм, позволяющий корректно запросить остановку потока: для этого вызывается метод interrupt(). Данный метод не прерывает поток напрямую, а устанавливает специальный флаг прерывания — своего рода вежливую просьбу завершить работу.

Важно понимать ключевые нюансы этого механизма:

  1. Метод interrupt() лишь сигнализирует, но не принудительно останавливает поток. Реакция на сигнал полностью зависит от кода самого потока.

  2. Необходима явная проверка флага. Если в коде потока не реализована проверка состояния флага прерывания (через isInterrupted() или interrupted()), запрос на остановку будет проигнорирован.

  3. Неблокирующие операции не прерываются мгновенно. Если поток выполняет вычисления, не связанные с блокирующими вызовами, они продолжатся до завершения, несмотря на установленный флаг.

  4. Блокирующие операции реагируют немедленно. Как только поток с установленным флагом прерывания попытается выполнить блокирующую операцию (например, sleep()), произойдёт следующее:

операция будет прервана; будет выброшено исключение InterruptedException.

Таким образом, корректная обработка прерывания требует:

  1. явного вызова interrupt() для отправки сигнала;

  2. регулярной проверки флага прерывания в коде потока;

  3. обработки InterruptedException в блокирующих операциях.

P/S: Метод stop() (который когда‑то существовал) официально объявлен устаревшим и запрещён к использованию.

→ Ссылка
Автор решения: Serge3leo

Как было уже сказано, начиная с 2018 года (JDK 11) метод stop() для потоков перестал работать (был объявлен устаревшим с 1999, JDK 1.2). Обоснование этому можно прочитать Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?, грубо говоря, оказалось, что не в человеческих силах это: отладить код обработки исключения ThreadDeath, таким образом, что бы можно было запустить поток заново или хоть как-то продолжить работу. Да и вообще ключевое слово throws теряло смысл, плюс ряд идеологических проблем.

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

  • Кооперативное завершение потока;
  • Завершение потока, запущенного в отдельной JVM (вместе с JVM).

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

Кооперативное завершение потока

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

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

ИМХО стоит делать как-то так:

public class CooperativeThread extends Thread {
    private volatile boolean stop_request;
    
    @Override
    public void run() {
        stop_request = false;
        while (!stop_request) {
            //+ Чужой код, периодически виснет, доступа к нему нет
            try {
                while (true) {
                    System.out.println("Остановите меня!!!!!");
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                System.out.println("Ождаемое исключение в чужом коде");
                e.printStackTrace();
            }
            //- Чужой код, периодически виснет, доступа к нему нет
            
            // При кооперативном завершении потока не факт, что будет
            // установлен isInterrupted(). И в данном примере, это
            // не так:
            assert !Thread.currentThread().isInterrupted();
        }
        System.out.println(this.getClass().getSimpleName() + ": хорь, остановка");
    }
    public void cooperative_stop() {
        stop_request = true;
        // TODO: Необходимо установить флаг для чужого кода или как-то иначе
        // ему сообщить, что время его истекло (может, сокет закрыть и т.п.)
        // В данном примере вероятность успеха 99% или около того.
        this.interrupt();
    }
}
public class CooperativeStop {
    public static void main(String[] args) {
        try {
            var cr = new CooperativeThread();
            System.out.println("Привет мы из CooperativeStop!");
            cr.start();
            Thread.sleep(300);
            cr.cooperative_stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Завершение потока, запущенного в отдельной JVM

Это более затратный и сложный способ, и в реализации, и в отладке, зато только он может дать гарантии остановки, возможности повторного запуска и т.п.

import java.io.IOException;

public class JvmProcess {
    private ProcessBuilder processBuilder;
    private Process process;

    public JvmProcess() {
        // Упрощённый запуск JVM, смотри https://stackoverflow.com/q/1229605/8585880
        var sep = System.getProperty("file.separator");
        var cp = System.getProperty("java.class.path");
        var path = System.getProperty("java.home")
                + sep + "bin" + sep + "java";
        processBuilder = new ProcessBuilder(path, "-cp", cp,
                this.getClass().getName());
        processBuilder.inheritIO();
    }
    public void start() throws IOException {
        process = processBuilder.start();
    }
    public void stop() {
        process.destroy();
    }
    public int waitFor() throws InterruptedException {
        return process.waitFor();
    }
    public static void main(String[] args) {
        //+ Чужой код, периодически виснет, доступа к нему нет
        try {
            while (true) {
                System.out.println("JvmProcess.main: Остановите меня!!!!!");
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            System.out.println("JvmProcess.main: Ождаемое исключение в чужом коде");
            e.printStackTrace();
        }
        //- Чужой код, периодически виснет, доступа к нему нет      
    }
}
import java.io.IOException;

public class JvmStop {
    public static void main(String[] args) {
        try {
            var jp = new JvmProcess();
            System.out.println("Привет мы из JvmStop!");
            jp.start();
            var wjp = new Thread(() -> {
                try {
                    var rc = jp.waitFor();
                    System.out.printf("rc: %d\n", rc);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            wjp.start();
            Thread.sleep(300);
            jp.stop();
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }
}
→ Ссылка
Автор решения: Serge3leo

А вот как, по-хорошему, делать не стоит (поэтому это отдельный ответ), но иногда приходится.

если у потока нет возможности опроса флага?

Кооперативное завершение потока "из подворотни" для чужого кода, к которому нет доступа, может быть реализовано подменой части или всех методов JDK или самого этого кода. Т.е. суммируются все риски, как исключённого метода stop(), так и собственно кооперативного останова потока и, плюс, риски возможных влияний подмены методов.

Как действовать в тяжёлых случаях можно поискать по ключевым словам: Java Instrumentation, Javassist, ASM и др. Однако, для конкретно вашего минимального примера, можно обойтись малой кровью - подменить System.out.

import java.io.PrintStream;
import java.util.concurrent.atomic.AtomicBoolean;

public class HackThread extends Thread {
    private static volatile ThreadLocal<AtomicBoolean> tls_stop_request = new ThreadLocal<>();
    private AtomicBoolean stop_request = new AtomicBoolean();
    
    private class HackThreadDeath extends RuntimeException {
        private static final long serialVersionUID = 472177517818940234L;

        public HackThreadDeath(String message) {
            super(message);
        }
    }
    
    private class HackStream extends PrintStream {      
        public HackStream(PrintStream ps) {
            super(ps);
        }
        private void checkTsr() {
            var tsr = tls_stop_request.get();
            if (tsr != null && tsr.get()) {
                throw new HackThreadDeath("from HackStream");
            }
        }
        public void write(byte[] buf, int off, int len) {
            checkTsr();
            super.write(buf, off, len);
        }
        public void flush() {
            checkTsr();
            super.flush();
        }
    }
    
    private static HackThread hackThread = new HackThread();
    
    static {
        System.setOut(hackThread.new HackStream(System.out));
    };
    
    @Override
    public void run() {
        stop_request.set(false);
        tls_stop_request.set(stop_request);
        assert tls_stop_request.get() == stop_request;
        assert System.out.getClass().getName() == "HackThread$HackStream";
        try {
            while (!stop_request.get()) {
                //+ Чужой код, периодически виснет, доступа к нему нет
                while (true) {
                    System.out.println("Держите меня!!!!!");
                }
                //- Чужой код, периодически виснет, доступа к нему нет          
            }
        } catch (HackThreadDeath e) {
        }
        tls_stop_request.set(null);
        System.out.println(this.getClass().getSimpleName() + ": хорь, остановка");
    }
    public void cooperative_stop() {
        assert tls_stop_request.get() != stop_request;
        stop_request.set(true);
        // В данном примере не нужен this.interrupt();
    }
}
public class HackStop {
    public static void main(String[] args) {
        try {
            var ht = new HackThread();
            System.out.println("Привет мы из HackStop!");
            ht.start();
            Thread.sleep(1);
            ht.cooperative_stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
→ Ссылка