Как остановить поток в java
Ищу ответ на вопрос как остановить поток в многопоточной системе, если код, в цикле (с флагом прерывания) завис
public class Processor implements Runnable {
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted())
break;
while (true) {//чужой код, периодически виснет, доступа к нему нет
System.out.println("Stop My!!!!!");//чужой код, периодически виснет, доступа к нему нет
}//чужой код, периодически виснет, доступа к нему нет
}
}
}
Как остановить поток в таком случае, если у потока нет возможности опроса флага?
Ответы (3 шт):
Поток невозможно прервать принудительно — он может завершиться только по собственной инициативе.
Однако существует механизм, позволяющий корректно запросить остановку потока: для этого вызывается метод interrupt(). Данный метод не прерывает поток напрямую, а устанавливает специальный флаг прерывания — своего рода вежливую просьбу завершить работу.
Важно понимать ключевые нюансы этого механизма:
Метод interrupt() лишь сигнализирует, но не принудительно останавливает поток. Реакция на сигнал полностью зависит от кода самого потока.
Необходима явная проверка флага. Если в коде потока не реализована проверка состояния флага прерывания (через isInterrupted() или interrupted()), запрос на остановку будет проигнорирован.
Неблокирующие операции не прерываются мгновенно. Если поток выполняет вычисления, не связанные с блокирующими вызовами, они продолжатся до завершения, несмотря на установленный флаг.
Блокирующие операции реагируют немедленно. Как только поток с установленным флагом прерывания попытается выполнить блокирующую операцию (например, sleep()), произойдёт следующее:
операция будет прервана; будет выброшено исключение InterruptedException.
Таким образом, корректная обработка прерывания требует:
явного вызова interrupt() для отправки сигнала;
регулярной проверки флага прерывания в коде потока;
обработки InterruptedException в блокирующих операциях.
P/S: Метод stop() (который когда‑то существовал) официально объявлен устаревшим и запрещён к использованию.
Как было уже сказано, начиная с 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();
}
}
}
А вот как, по-хорошему, делать не стоит (поэтому это отдельный ответ), но иногда приходится.
если у потока нет возможности опроса флага?
Кооперативное завершение потока "из подворотни" для чужого кода, к которому нет доступа, может быть реализовано подменой части или всех методов 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();
}
}
}