Если вы изменили значение свойства, слушатели вызывают в том же потоке, из которого было изменено свойство (просто подумайте о том, как вы могли бы/могли бы реализовать классы свойств). Поэтому в вашем примере ошибка ThreadDeath
выбрасывается из потока, поддерживающего экземпляр Timer
, что на самом деле не так, как вы планировали.
Правильный способ прервать поток извне (к этому потоку) - установить флаг, а затем в потоке выполнить опрос флаг с регулярными интервалами. Это на самом деле сложнее, чем кажется, потому что для доступа к флагам требуется несколько потоков, поэтому доступ к нему должен быть правильно синхронизирован.
К счастью, для этого есть некоторые классы полезности. FutureTask
, например, обертывает Runnable
или Callable
и предоставляет методы cancel()
и isCancelled()
. Если вы используете JavaFX, то javafx.concurrent API предоставляет некоторые реализации Callable
и Runnable
, которые делают это, а также предоставляют функциональные возможности для выполнения кода в потоке приложения FX. Посмотрите на пример documentation for javafx.concurrent.Task.
Так, например, вы могли бы сделать:
package javatest;
public class TestStopMethod implements Runnable {
@Override
public void run() {
try {
synchronized(this) {
for(;;) {
wait(1);
}
}
} catch (InterruptedException exc) {
System.out.println("Interrupted");
}
}
}
Watchdog.java:
package javatest;
import java.util.TimerTask;
import java.util.concurrent.Future;
public class Watchdog extends TimerTask {
Future<Void> watched;
public Watchdog(Future<Void> target) {
watched = target;
}
@Override
public void run() {
watched.cancel(true);
//watched.stop(); <- Works but this is exactly what I am trying to avoid
System.out.println("You're dead!");
}
}
Driver.java:
package javatest;
import java.util.*;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Driver {
public static void main(String[] args) {
try {
FutureTask<Void> myTask = new FutureTask<>(new TestStopMethod(), null);
Timer t = new Timer();
Watchdog w = new Watchdog(myTask);
t.schedule(w, 1000);
Thread mythread = new Thread(myTask);
mythread.start();
mythread.join();
t.cancel();
System.out.println("End of story");
} catch (InterruptedException ex) {
Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
В приложении JavaFX, вы могли бы сделать это так. Обратите внимание, что все идет плохо, если вы пытаетесь сделать это без использования потока приложений FX, потому что флаг cancelled
в FX Task
должен быть обновлен в этом потоке.
package javatest;
import javafx.concurrent.Task;
public class TestStopMethod extends Task<Void> {
@Override
public Void call() {
System.out.println("Calling");
while (true) {
if (isCancelled()) {
System.out.println("Cancelled");
break ;
}
}
System.out.println("Exiting");
return null ;
}
}
Watchdog.java:
package javatest;
import java.util.TimerTask;
import javafx.concurrent.Task;
public class Watchdog extends TimerTask {
Task<Void> watched;
public Watchdog(Task<Void> target) {
watched = target;
}
@Override
public void run() {
watched.cancel();
//watched.stop(); <- Works but this is exactly what I am trying to avoid
System.out.println("You're dead!");
}
}
Driver.java
package javatest;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Driver extends Application {
@Override
public void start(Stage primaryStage) {
try {
TextArea console = new TextArea();
BorderPane root = new BorderPane(console);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Task<Void> myTask = new TestStopMethod();
Timer t = new Timer();
Watchdog w = new Watchdog(myTask);
t.schedule(w, 1000);
Thread mythread = new Thread(myTask);
mythread.setDaemon(true);
myTask.stateProperty().addListener((obs, oldState, newState) -> {
console.appendText("State change "+oldState+" -> "+newState+"\n");
if (oldState == Worker.State.RUNNING) {
t.cancel();
console.appendText("End of Story\n");
}
});
mythread.start();
} catch (Exception ex) {
Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Какой поток вы думаете, бросает 'ThreadDeath' ошибку? –
Я подумал, что если я добавлю слушателя к свойству Property и изменим его значение в WatchDog, он поднимет это исключение. Код, как сейчас, продолжает работать, поэтому 'mythread.join()' никогда не вызывается. – DeMarco
Он будет выброшен из потока, который изменяет свойство, которое является потоком, поддерживающим экземпляр таймера. (Если вы подумаете об этом, было бы невозможно запланировать прослушивание слушателя на произвольном потоке.) Я думаю, вы запутываете объект, которому принадлежит метод, с потоком, на котором выполняется этот метод. –