JavaFX中的复杂并发:使用来自多个工作线程的ObservableLists和Properties

我有多个工作线程和一个JavaFX GUI,它报告这些线程中发生的事情。

线程之间共享了大量数据,需要进行可视化。 所以我使用ObservableList和Property来能够轻松地在JavaFX中显示数据。

我做了一个小示例应用程序,以显示类似于我的应用程序中发生的事情。 它有2个列表,工作线程将数据从一个列表移动到另一个列表。 状态字符串保持最新。 完整的示例代码可以在http://codetidy.com/6569/找到(此代码将崩溃,请参阅后面的内容)

以下是共享的ObservableList和属性:

private ObservableList newItems; private ObservableList readyItems; private StringProperty status; 

以下是它们在JavaFX中的使用方式:

 listViewA.setItems(processor.getNewItems()); listViewB.setItems(processor.getReadyItems()); statusLabel.textProperty().bind(processor.getStatus()); 

工作线程更新了这些列表和属性,但当然,它需要在JavaFX线程上执行此操作,这就是事情变得丑陋的地方。 如果我不需要在JavaFX线程上更新,这将是代码:

  Runnable newItemAdder = new Runnable() { @Override public void run() { while(true) { synchronized (newItems) { String newItem = checkForNewItem(); //slow if (newItem != null) { newItems.add(newItem); newItems.notify(); } if (newItems.size() >= 5) status.set("Overload"); else status.set("OK"); } synchronized (readyItems) { if (readyItems.size() > 10) readyItems.remove(0); } try { Thread.sleep(200); } catch (InterruptedException e) { return; } } } }; new Thread(newItemAdder).start(); Runnable worker = new Runnable() { @Override public void run() { while(true) { List toProcess = new ArrayList(); synchronized (newItems) { if (newItems.isEmpty()) try { newItems.wait(); } catch (InterruptedException e) { return; } toProcess.addAll(newItems); } for (String item : toProcess) { String processedItem = processItem(item); //slow synchronized (readyItems) { readyItems.add(processedItem); } } } } }; new Thread(worker).start(); 

当然,使用Platform.runLater可以轻松解决一些问题:

  Platform.runLater(new Runnable() { @Override public void run() { synchronized (newItems) { if (newItems.size() >= 5) status.set("Overload"); else status.set("OK"); } } }); 

这对于我只在任务中写入的属性/列表来说很好,只能在JavaFX GUI中读取。 但是对于此示例中的列表来说,执行此操作非常复杂,您需要对其进行同步,读取和写入。 你需要添加很多Platform.runLater,你需要阻止,直到“runLater”任务完成。 这会导致非常复杂且难以读写的代码(我设法以这种方式运行此示例,请参阅我的意思: http : //codetidy.com/6570/ )。

有没有其他方法让我的榜样有效? 我很欣赏任何其他解决方案或部分解决方案……

背景资料

任务javadoc包括许多用于在JavaFX中的线程之间传递数据的并发使用模式。

任务包括便捷数据传输方法(如updateMessage) ,可以使用具有用户定义状态属性的Runnable代替。

在适当的时候,请考虑使用专为并发而设计的集合结构,例如BlockingQueue 。 另一个优点是BlockingQueues可以有大小限制,这似乎是你想要的。

一些一般性建议

  1. 在多个线程中使用可变的可观察项时要非常小心。 很容易无意中触发导致竞争条件的更新,从应用程序线程更新活动场景图以及其他线程问题。
  2. 尽可能使用不可变数据而不是可变的可观察项。
  3. 利用JavaFX并发和java.util.concurrent库中的一些更高级别的实用程序。
  4. 尽可能避免显式同步和通知语句。
  5. 小心将同步或其他可能的阻塞语句放在JavaFX应用程序线程上运行的代码中 – 因为您可能会使GUI无响应。
  6. 仅在需要与JavaFX线程交互时才使用JavaFX并发实用程序。
  7. 使用标准Java并发实用程序从JavaFX线程执行大量非常复杂的multithreading处理。 有一个协调JavaFX任务来合并和控制UI反馈。

以上只是经验法则,不需要在教学上遵循。

合理复杂的线程样本

  • 一个图表渲染器 ,演示了上面的一些原则来渲染300个图表,同时仍然保持UI响应进度更新和用户交互。

完整示例的原始链接和jewelsea的示例解决方案已经死了,所以我将添加一个答案,简要总结一下我最终做的事情。

为了简化这一点,我们假设你从一个包含数据模型的类开始(让我们称之为DataModel )。 更改它的多个线程使用此类的实例。

现在的问题是你想在javaFX中使用数据模型,但你不能简单地改变你的数据模型以使用PropertyObservableList等。如果这样做,将从非javafx线程调用侦听器,并且GUI绑定到他们会抛出exception。

相反,您需要为javaFX创建一个单独的数据模型类。 这只是原始版本的JavaFX版本(我们称之为FXDataModel )。 所以这个版本包含相同的信息,但它使用javaFX PropertyObservableList 。 这样,您就可以将GUI绑定到它。

下一步是使用DataModel实例定期更新FXDataModel实例。 为此,您需要向FXDataModel添加update(DataModel dataModel)方法, FXDataModel将数据从原始数据模型复制到FXDataModel实例中。 必须始终在javaFX线程上调用此更新函数。 最后,您需要做的就是定期调用该更新函数。

在我的实际场景中,我每隔200毫秒调用一次更新函数,这足以能够在GUI中显示数据模型的实时视图。 (如果您想要的不仅仅是数据模型的视图,并且您想要从GUI更改内容,那么事情会变得更复杂,但这不是我需要做的事情)