在PropertyListener中杀死一个线程(JavaFX8)
我知道Java的实际模型是用于协作线程的,它强制线程死亡不会发生。在PropertyListener中杀死一个线程(JavaFX8)
由于Thread.stop()
已弃用(基于上述原因)。我试图通过一个BooleanProperty监听器来停止线程。
这里是MCVE:
TestStopMethod.java
package javatest;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
public class TestStopMethod extends Thread {
private BooleanProperty amIdead = new SimpleBooleanProperty(false);
public void setDeath() {
this.amIdead.set(true);
}
@Override
public void run() {
amIdead.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
System.out.println("I'm dead!!!");
throw new ThreadDeath();
});
for(;;);
}
}
WatchDog.java
package javatest;
import java.util.TimerTask;
public class Watchdog extends TimerTask {
TestStopMethod watched;
public Watchdog(TestStopMethod target) {
watched = target;
}
@Override
public void run() {
watched.setDeath();
//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.logging.Level;
import java.util.logging.Logger;
public class Driver {
public static void main(String[] args) {
try {
TestStopMethod mythread = new TestStopMethod();
Timer t = new Timer();
Watchdog w = new Watchdog(mythread);
t.schedule(w, 1000);
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);
}
}
}
如果更改属性值,则会在属性发生更改的相同线程上调用侦听器(请考虑如何实现属性类)。因此,在您的示例中,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应用程序线程的情况下执行此操作,情况会变糟,因为必须在该线程上更新FX Task
中的cancelled
标志。
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'错误? – 2014-10-08 18:05:25
我想,如果我添加一个监听器到一个属性并在WatchDog中改变它的值,它会引发这个异常。现在的代码保持运行,所以'mythread.join()'永远不会被调用。 – DeMarco 2014-10-08 18:09:56
它将从改变属性的线程抛出,这是支持定时器实例的线程。 (如果你仔细想想,基本上不可能安排在任意线程上调用监听器。)我认为你将拥有该方法的对象与执行该方法的线程混淆了。 – 2014-10-08 18:12:01