JavaFX/Gluon多线程wait/notify报错:NOT ON FX APPLICATION THREAD求助
问题分析与解决方案
核心错误原因
JavaFX强制要求所有UI组件的修改操作必须在FX应用线程(UI线程)中执行,你的appThread是自定义后台线程,直接调用label_view.setText()违反了这个规则,导致"NOT ON FX APPLICATION THREAD"报错。
修复步骤
- 用
Platform.runLater()包装所有UI更新代码,确保它们在FX线程执行 - 优化线程同步逻辑,避免潜在死锁或线程安全问题
- 修正代码中的拼写错误(如
@READOUBLE应为@READDOUBLE)
修改后的代码示例
appThread.java
package com.knn.client; import com.knnapplication.views.ExampleView; import java.io.IOException; import javafx.application.Platform; import javafx.scene.control.Label; public class appThread implements Runnable { private final Label label_view; private final Thread th; private final Object lock = new Object(); public Object getLock() { return lock; } public appThread(Label label) { this.label_view = label; this.th = new Thread(this); th.start(); System.out.println("Thread start()"); } @Override public void run() { String risposta = ""; try { ExampleView.getClient().getOut().writeObject(1); ExampleView.getClient().getOut().writeObject("servo.dat"); } catch (IOException e) { throw new RuntimeException(e); } boolean flag = true; do { try { risposta = ExampleView.getClient().getIn().readObject().toString(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } System.out.println(risposta); if (!risposta.contains("@ENDEXAMPLE")) { if (risposta.equals("@READSTRING")) { // 用Platform.runLater包装UI更新,确保在FX线程执行 Platform.runLater(() -> label_view.setText("Inserisci valore discreto: ")); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重置中断状态 throw new RuntimeException(e); } } } else if (risposta.equals("@READDOUBLE")) { // 修正拼写错误 double x = 0.0; do { Platform.runLater(() -> label_view.setText("Inserisci valore continuo")); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } // 建议将输入值获取逻辑移至Presenter,避免后台线程依赖UI组件 } while (Double.isNaN(x)); } } else { flag = false; } } while (flag); } }
ExamplePresenter.java
package com.knnapplication.views; import com.gluonhq.charm.glisten.application.AppManager; import com.gluonhq.charm.glisten.control.AppBar; import com.gluonhq.charm.glisten.control.TextField; import com.gluonhq.charm.glisten.mvc.View; import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; import com.knn.client.appThread; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import java.io.IOException; public class ExamplePresenter { @FXML private View exampleView; @FXML private Button send_sample; @FXML private Button start_example; @FXML private TextField sample_prompt; @FXML private VBox _input_section; @FXML private Label label; private appThread currentThread; @SuppressWarnings("removal") public void initialize() { exampleView.showingProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { AppBar appBar = AppManager.getInstance().getAppBar(); appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> AppManager.getInstance().getDrawer().open())); appBar.setTitleText("Inserisci esempio:"); // 此处更新UI安全,因为回调在FX线程执行 label.setText(ExampleView.getFile().toString()); } // 仅在视图首次显示时创建线程,避免重复实例化 if (newValue && currentThread == null) { currentThread = new appThread(label); } }); } @FXML void sendExample(ActionEvent event) throws IOException { ExampleView.getClient().getOut().writeObject(sample_prompt.getText().trim()); synchronized (currentThread.getLock()) { currentThread.getLock().notify(); } // 此处更新UI安全,因为事件回调在FX线程执行 label.setText("Valore inserito correttamente!"); } @FXML void startExample(ActionEvent event) throws IOException, ClassNotFoundException, InterruptedException { // 可在此处初始化服务器连接或启动线程(若未提前启动) } }
额外优化建议
- 避免在后台线程中持有UI组件引用,可通过回调或事件通知Presenter更新UI
- 优先使用JavaFX自带的
Task或Service并发工具,它们内置线程管理和UI同步支持 - 处理线程中断时,务必重置中断状态(
Thread.currentThread().interrupt()),防止后续代码忽略中断信号
内容的提问来源于stack exchange,提问作者Luigi Daddario
相关产品推荐
相关产品推荐

