我自己可以解决此问题。
System.out在各个地方添加几天后,结果发现问题出在
isUpdating变量的并发问题上。当JavaFX线程位于
while循环和中的
synchronized块之间时,发生了问题
Updater.run。我通过使
Updater.run和
GUIUpdater.scheduleUpdate方法在同一对象上同步来解决了这个问题。
我也将其
GUIUpdater制成了仅静态对象,因为
Runnables无论其他
GUIUpdater实例如何,都有多个实例将放置在JavaFX事件队列中,从而阻塞了事件队列。总而言之,这是结果
GUIUpdater类:
package be.pbeckers.javafxguiupdater;import java.util.concurrent.ConcurrentlinkedQueue;import javafx.application.Platform;import javafx.beans.property.Property;import javafx.beans.value.ChangeListener;import javafx.beans.value.Observablevalue;public abstract class GUIUpdater { private static ConcurrentlinkedQueue<PropertyUpdater<?>> dirtyPropertyUpdaters = new ConcurrentlinkedQueue<>(); private static Updater updater = new Updater(); private static boolean isUpdating = false; public static <T> void bind(Property<T> property, Observablevalue<T> observable) { PropertyUpdater<T> propertyUpdater = new PropertyUpdater<>(property, observable); observable.addListener(propertyUpdater); } public static <T> void unbind(Property<T> property, Observablevalue<T> observable) { PropertyUpdater<T> tmpPropertyUpdater = new PropertyUpdater<>(property, observable); observable.removeListener(tmpPropertyUpdater); } private static synchronized void scheduleUpdate(PropertyUpdater<?> updater) { GUIUpdater.dirtyPropertyUpdaters.add(updater); if (!GUIUpdater.isUpdating) { GUIUpdater.isUpdating = true; Platform.runLater(GUIUpdater.updater); } } private static class PropertyUpdater<T> implements ChangeListener<T> { private boolean isDirty = false; private Property<T> property = null; private Observablevalue<T> observable = null; public PropertyUpdater(Property<T> property, Observablevalue<T> observable) { this.property = property; this.observable = observable; } @Override public synchronized void changed(Observablevalue<? extends T> observable, T oldValue, T newValue) { if (!this.isDirty) { this.isDirty = true; GUIUpdater.scheduleUpdate(this); } } public synchronized void update() { T value = this.observable.getValue(); this.property.setValue(value); this.isDirty = false; } @Override public boolean equals(Object otherObj) { PropertyUpdater<?> otherUpdater = (PropertyUpdater<?>) otherObj; if (otherObj == null) { return false; } else { // only compare addresses (comparing with equals also compares contents): return (this.property == otherUpdater.property) && (this.observable == otherUpdater.observable); } } } private static class Updater implements Runnable { @Override public void run() { synchronized (GUIUpdater.class) { // Loop through the individual PropertyUpdaters, updating them one by one: while(!GUIUpdater.dirtyPropertyUpdaters.isEmpty()) { PropertyUpdater<?> curUpdater = GUIUpdater.dirtyPropertyUpdaters.poll(); curUpdater.update(); } // Mark as updated: GUIUpdater.isUpdating = false; } } }}这是测试器类的稍有更新的版本(由于它完全不重要,因此我不对其进行详细介绍):
package be.pbeckers.javafxguiupdater.test;import be.pbeckers.javafxguiupdater.GUIUpdater;import javafx.application.Application;import javafx.beans.property.SimpleStringProperty;import javafx.concurrent.Task;import javafx.event.ActionEvent;import javafx.event.EventHandler;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.control.ProgressBar;import javafx.scene.layout.FlowPane;import javafx.stage.Stage;public class JavaFXTest extends Application { private Label lblCurFile = new Label(); private Label lblErrors = new Label(); private Label lblBytesParsed = new Label(); private ProgressBar prgProgress = new ProgressBar(); public static void main(String args[]) { JavaFXTest.launch(args); } @Override public void start(Stage primaryStage) throws Exception { // Init window: FlowPane flowPane = new FlowPane(); primaryStage.setScene(new Scene(flowPane)); primaryStage.setTitle("JavaFXTest"); // Add a few Labels and a progressBar: flowPane.getChildren().add(this.lblCurFile); flowPane.getChildren().add(this.lblErrors); flowPane.getChildren().add(this.lblBytesParsed); flowPane.getChildren().add(this.prgProgress); // Add button: Button btnStart = new Button("Start"); btnStart.setonAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // Create task: TestTask testTask = new TestTask(); // Bind: GUIUpdater.bind(JavaFXTest.this.lblCurFile.textProperty(), testTask.curFileProperty()); GUIUpdater.bind(JavaFXTest.this.lblErrors.textProperty(), testTask.errorsProperty()); GUIUpdater.bind(JavaFXTest.this.lblBytesParsed.textProperty(), testTask.bytesParsedProperty()); JavaFXTest.this.prgProgress.progressProperty().bind(testTask.progressProperty()); // No need to use GUIUpdater here, Task class provides the same functionality for progress. // Start task: Thread tmpThread = new Thread(testTask); tmpThread.start(); } }); flowPane.getChildren().add(btnStart); // Show: primaryStage.show(); } private class TestTask extends Task<Void> { private SimpleStringProperty curFile = new SimpleStringProperty(); private SimpleStringProperty errors = new SimpleStringProperty(); private SimpleStringProperty bytesParsed = new SimpleStringProperty(); @Override protected Void call() throws Exception { // Count: try { int maxValue = 1000000; long startTime = System.currentTimeMillis(); System.out.println("Starting..."); for(int i = 0; i < maxValue; i++) { this.updateProgress(i, maxValue - 1); // Simulate some progress variables: this.curFile.set("File_" + i + ".txt"); if ((i % 1000) == 0) { //this.errors.set("" + (i / 1000) + " Errors"); } //this.bytesParsed.set("" + (i / 1024) + " KBytes"); } long stopTime = System.currentTimeMillis(); System.out.println("Done in " + (stopTime - startTime) + " msec!"); } catch(Exception e) { e.printStackTrace(); } // Unbind: GUIUpdater.unbind(JavaFXTest.this.lblCurFile.textProperty(), this.curFileProperty()); GUIUpdater.unbind(JavaFXTest.this.lblErrors.textProperty(), this.errorsProperty()); GUIUpdater.unbind(JavaFXTest.this.lblBytesParsed.textProperty(), this.bytesParsedProperty()); return null; } public SimpleStringProperty curFileProperty() { return this.curFile; } public SimpleStringProperty errorsProperty() { return this.errors; } public SimpleStringProperty bytesParsedProperty() { return this.bytesParsed; } }}


