【问题标题】:Complex concurrency in JavaFX: using ObservableLists and Properties from multiple worker threadsJavaFX 中的复杂并发:使用来自多个工作线程的 ObservableLists 和 Properties
【发布时间】:2013-09-03 00:58:42
【问题描述】:

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

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

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

这里是共享的 ObservableList 的 & 属性:

private ObservableList<String> newItems;
private ObservableList<String> 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<String> toProcess = new ArrayList<String>();
              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/)。

还有其他方法可以让我的示例正常工作吗?我将不胜感激任何其他解决方案或部分解决方案...

【问题讨论】:

  • 您的代码有哪些变化?你能展示给他们看吗? JewelSea 的最后一个链接失效了
  • @AloneInTheDark 我环顾四周,但恐怕我没有原始示例代码,也没有jewelsea 的修改版本。我已经发布了一个带有解决方案摘要的额外答案。

标签: java multithreading javafx-2


【解决方案1】:

背景信息

Task javadoc 包含多种并发使用模式,用于在 JavaFX 中的线程之间传递数据。

Task 包括方便的数据传输方法,例如updateMessage,并且可以使用用户定义的状态属性代替 Runnable。

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

一些一般性建议

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

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

相当复杂的线程示例

  • chart renderer 演示了上述一些原则以呈现 300 个图表,同时仍保持 UI 响应进度更新和用户交互。

【讨论】:

  • +1 以获得良好的一般建议。 JavaFX 中的 GUI 元素必须符合 JavaFX Bean 样式模式,这要求它们始终是可变的。此外,JavaFX 中的控制器类必须始终是公共的。对可变性的要求、封装的丢失以及 GUI 元素只能在 FX 应用程序线程上修改的要求使得高并发应用程序的环境看似非常不利。
  • 您的解决方案将实际数据和 GUI 中显示的数据分开,并添加一个线程,将更新从一个发送到另一个。这似乎是额外的工作,但我想这是实现这类事情的最干净的方法。它确实使这一切变得很多不那么复杂。我也喜欢您使用的 BlockingQueue 等,它使代码更简单。我不知道这个类,我将不得不检查 java.util.concurrent 中还有哪些其他有用的东西......非常感谢这个出色的答案!
  • 我已经完成了对我的真实应用程序的更改。它基本上已被拆分为没有 JavaFX 的多线程代码和一个定期更新 GUI 的线程(而不是直接更新 GUI,它更新“JavaFX 样式模型”,带有属性和 ObservableLists 等)。从“多线程模型”定期更新“javaFX 样式模型”的部分,是 300 多行代码(因为需要显示大量数据)。那是很多额外的代码,但它是简单易读的代码,而且多线程代码现在比以前复杂得多。所以我对这个解决方案很满意。
  • jewelsea,你的最后一个链接失效了。你能把它重新上传到某个地方吗?
  • Aloneintgdark,不幸的是,断开链接处的代码不再存在,所以我无法重新上传。我编辑了答案以删除损坏的链接。
【解决方案2】:

jewelsea 提供的完整示例和示例解决方案的原始链接已失效,因此我将添加一个答案,并简要说明我最终做了什么。

为了简单起见,假设您从 1 个保存数据模型的类开始(我们称之为 DataModel)。这个类的一个实例被多个改变它的线程使用。

现在的问题是您想在 javaFX 中使用数据模型,但您不能简单地将数据模型更改为使用 PropertyObservableList 等。如果这样做,将从非javafx 线程,绑定到它们的 GUI 会抛出异常。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2020-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多