我正在使用JavaFX制作一个简单的按钮游戏,我需要一个线程来运行,以便我可以检查敌人或玩家是否还活着。我的问题不是启动和使用线程,而是在窗口关闭时停止它。
我做了一个样本课来证明我的意思。
private Thread thread;
private boolean running;
private Stage window;
public void run() {
while (running) {
System.out.println("Hello");
}
stopThread();
}
public synchronized void startThread() {
running = true;
thread = new Thread(this, "Monitor");
thread.start();
}
public synchronized void stopThread() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void start(Stage stage) throws Exception {
window = new Stage();
window = stage;
Pane layout = new Pane();
Scene scene = new Scene(layout);
window.setOnCloseRequest(e -> {
e.consume();
close();
});
window.setScene(scene);
window.show();
}
public void close() {
window.close();
stopThread();
}
public static void main(String[] args) {
Things things = new Things();
things.startThread();
launch(args);
}
当我运行它时,“Hello”连续打印,但当我尝试关闭它时,线程继续运行,eclispe进入调试模式说:
Thread [JavaFX Application Thread] (Suspended (exception NullPointerException))
QuantumToolkit.runWithoutRenderLock(Supplier<T>) line: not available
GlassWindowEventHandler.handleWindowEvent(Window, long, int) line: not available
WinWindow(Window).handleWindowEvent(long, int) line: not available
WinWindow(Window).notifyClose() line: not available
WinApplication._runLoop(Runnable) line: not available [native method]
WinApplication.lambda$null$145(Runnable) line: not available
2091156596.run() line: not available
Thread.run() line: not available
我查看了我的代码,但找不到任何null的东西。
答案 0 :(得分:1)
假设Things
是您展示的课程的名称,您并未停止正确的主题。
当你致电launch(args)
时,FX Toolkit会创建一个应用程序类的实例(我假设它是Things
),创建一个Stage
,并传递{ {1}}到应用程序实例的Stage
方法(在FX应用程序线程上执行该方法)。
因此,您创建一个实例,并在该实例上启动该线程。然后FX工具包创建第二个实例,并且在该实例上您试图阻止该线程。所以你要停止与你开始的线程不同的线程。
解决此问题的方法是删除行
start(...)
来自Things things = new Things();
things.startThread();
方法,只需添加
main(...)
到this.startThread();
方法的开头。
此外,正如@RyanJ指出的那样,当你有一个线程等待第二个线程完成时,你就会死锁,并且两者都在尝试执行相同的start(...)
方法。此外,您应该将synchronized
声明为running
,因为它是从多个线程访问的:
volatile
这样可行:
private volatile boolean running ;
最后一点:如果这是您打开的唯一窗口,只要没有非守护程序线程在运行,JVM就会在您关闭窗口时退出。所以你可以让JVM通过使它成为一个守护程序线程来杀死你的线程。如果这适用于您的真实应用,您可以通过覆盖import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Things extends Application implements Runnable {
private Thread thread;
private volatile boolean running;
private Stage window;
@Override
public void run() {
while (running) {
System.out.println("Hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// stopThread();
running = false ;
}
public synchronized void startThread() {
running = true;
thread = new Thread(this, "Monitor");
thread.start();
}
public synchronized void stopThread() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void start(Stage stage) throws Exception {
startThread();
window = stage;
Pane layout = new Pane();
Scene scene = new Scene(layout);
window.setOnCloseRequest(e -> {
e.consume();
close();
});
window.setScene(scene);
window.show();
}
public void close() {
window.close();
stopThread();
}
public static void main(String[] args) {
launch(args);
}
}
方法进行所需的任何清理:
stop
答案 1 :(得分:0)
你可能会遇到活锁/死锁情况。
在run方法中,while循环终止后,你的线程会尝试自行停止,然后尝试等待自己死掉。这是一个同步方法,意味着一次只能运行一个线程。
当您从main方法调用它并尝试停止该线程时,它将尝试加入该线程并等待它死亡。由于第一次调用已经在执行stopThread
方法,当while循环在run方法中终止时,它将停留在那里并阻塞,不会导致线程死亡。由于线程没有死亡,对stopThread
的第一次调用将无法完成。
您可以从stopThread
方法移除run
,它应该会表现得更好,但是根据您在此处发布的内容,看起来更好的设计可能是有序的。如果我的建议有效,我会很好奇。
进一步检查后,您的线程中的静态/非静态数据也会出现问题。
我假设您的Things
类实现了Runnable
,因此当您将其传递给Thread
构造函数并启动它时,它会获取其自己的副本堆栈,它是自己的局部变量。当您在stopThread
方法中调用close
时,您没有设置正确的running
变量(它不是运行的线程所见的变量)。如果您创建该字段static
并删除thread.join...
代码,则其行为正常。
编辑: 作为更好的设计选择,利用Threads / Runnables获得的功能以及通常在应用程序中管理它们的方式可能更明智(更多信息,因为您使用的是JFX,您应该查看服务/任务)
拆分线程实现和主类,并利用线程本身的功能来阻止它。
举个例子,这个例子非常清晰,并且效果很好:
public class Things implements Runnable {
private volatile boolean running; // this indicates it's running
public Things() {
running = true; // thread is running when it's created (though not really until "run" is called)
}
// called from start()
public void run() {
while (running) {
System.out.println("Hello");
}
}
// call this method to stop the thread, from outside on the object reference
public synchronized void stop() {
running = false;
}
}
在单独的Test / Application类中,您可以操纵Things
引用来执行您想要的操作,而无需保留对实际线程的引用。
public class Test extends Application {
private Stage window;
private static Things things;
public void start(Stage stage) throws Exception {
window = new Stage();
window = stage;
Pane layout = new Pane();
Scene scene = new Scene(layout);
window.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent windowEvent) {
close();
}
});
window.setScene(scene);
window.show();
}
// on close
public void close() {
window.close();
things.stop(); // stop the things object directly
}
public static void main(String[] args) {
things = new Things(); // create new runnable
Thread t = new Thread(things,"Monitor"); // create thread
t.start(); // start thread
launch(args); // launch app
}
}