7

我的代码TreeItem<String> 是在后台任务中创建的,因为我有很多它们,并且它们的创建需要相当长的时间才能使应用程序冻结。在此示例中,它没有多大意义,但它说明了我在实际应用程序中遇到的问题。扩展节点时,程序会抛出 ConcurrentModificationException。

我使用 jdk1.7.0_17 和 JavaFX 2.2.7

有谁知道如何创建线程安全Tree或如何规避问题?

例外

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at com.sun.javafx.collections.ObservableListWrapper$ObservableListIterator.next(ObservableListWrapper.java:681)
    at javafx.scene.control.TreeItem.updateExpandedDescendentCount(TreeItem.java:788)
    ...

代码

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.Random;


public class ConcurrentExample extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        TreeView<String> treeView = new TreeView<String>(createNode("root"));
        HBox hBox = new HBox();
        hBox.getChildren().addAll(treeView);
        Scene scene = new Scene(hBox);
        stage.setScene(scene);
        stage.show();
    }

    Random r = new SecureRandom();

    public TreeItem<String> createNode(final String b) {
        return new TreeItem<String>(b) {
            private boolean isLeaf;
            private boolean isFirstTimeChildren = true;
            private boolean isFirstTimeLeaf = true;

            @Override
            public ObservableList<TreeItem<String>> getChildren() {
                if (isFirstTimeChildren) {
                    isFirstTimeChildren = false;
                    buildChildren(super.getChildren());
                }
                return super.getChildren();
            }

            @Override
            public boolean isLeaf() {
                if (isFirstTimeLeaf) {
                    isFirstTimeLeaf = false;
                    isLeaf = r.nextBoolean() && r.nextBoolean() && r.nextBoolean();
                }
                return isLeaf;
            }

            private void buildChildren(final ObservableList<TreeItem<String>> children) {
                if (!this.isLeaf()) {
                    Task<Integer> task = new Task<Integer>() {
                        @Override
                        protected Integer call() throws Exception {
                            int i;
                            int max = r.nextInt(500);
                            for (i = 0; i <= max; i++) {
                                children.addAll(new TreeItem[]{createNode("#" + r.nextInt())});
                            }
                            return i;
                        }
                    };
                    new Thread(task).start();
                }
            }
        };
    }

}
4

3 回答 3

10

当前的答案没有帮助。关键是你必须在 Platform.runLater 的帮助下在主线程中执行子线程的更新

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.Random;


public class Example extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        TreeView<String> treeView = new TreeView<String>(createNode("root"));
        HBox hBox = new HBox();
        hBox.getChildren().addAll(treeView);
        Scene scene = new Scene(hBox);
        stage.setScene(scene);
        stage.show();
    }

    Random r = new SecureRandom();

    public TreeItem<String> createNode(final String b) {
        return new TreeItem<String>(b) {
            private boolean isLeaf;
            private boolean isFirstTimeChildren = true;
            private boolean isFirstTimeLeaf = true;

            @Override
            public ObservableList<TreeItem<String>> getChildren() {
                if (isFirstTimeChildren) {
                    isFirstTimeChildren = false;
                    buildChildren(super.getChildren());
                }
                return super.getChildren();
            }

            @Override
            public boolean isLeaf() {
                if (isFirstTimeLeaf) {
                    isFirstTimeLeaf = false;
                    isLeaf = r.nextBoolean() && r.nextBoolean() && r.nextBoolean();
                }
                return isLeaf;
            }

            private void buildChildren(final ObservableList<TreeItem<String>> children) {
                if (!this.isLeaf()) {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            int i;
                            int max = r.nextInt(500);
                            for (i = 0; i <= max; i++) {
                                children.addAll(new TreeItem[]{createNode("#" + r.nextInt())});
                            }
                        }
                    });
                }
            }
        };
    }
}

这个人在这里遇到了同样的问题:http ://blog.idrsolutions.com/2012/12/handling-threads-concurrency-in-javafx/

于 2013-06-30T21:51:20.673 回答
4

您不能从除 JavaFX 应用程序线程之外的任何线程直接修改任何影响活动节点和与场景图相关的数据(包括 TreeView 的项目)的任何内容。

有关示例任务(返回 ObservableList 或部分结果的任务),请参阅任务文档,这将帮助您解决问题。您需要在新的 ObservableList 中的 Task 中创建新的 TreeItems,然后,一旦 Task 完成(在 JavaFX 应用程序线程上),将树的项目列表设置为从 Task 返回的 ObservableList。

http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html

这是您的代码的更新版本,它遵循其中一些原则,并且没有任何 ConcurrentModificationExceptions。

为什么不应该在 TreeItem 调用 updateExpandedDescendentCount() 的那一刻准确地执行 addAll(List) 调用?

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.Random;


public class ConcurrentExample extends Application {
  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage stage) throws Exception {
    TreeView<String> treeView = new TreeView<>(createNode("root"));
    HBox hBox = new HBox();
    hBox.getChildren().addAll(treeView);
    Scene scene = new Scene(hBox);
    stage.setScene(scene);
    stage.show();
  }

  Random r = new SecureRandom();

  public TreeItem<String> createNode(final String b) {
    return new TreeItem<String>(b) {
      private boolean isLeaf;
      private boolean isFirstTimeChildren = true;
      private boolean isFirstTimeLeaf = true;

      @Override
      public ObservableList<TreeItem<String>> getChildren() {
        if (isFirstTimeChildren) {
          isFirstTimeChildren = false;
          buildChildren(super.getChildren());
        }
        return super.getChildren();
      }

      @Override
      public boolean isLeaf() {
        if (isFirstTimeLeaf) {
          isFirstTimeLeaf = false;
          isLeaf = r.nextBoolean() && r.nextBoolean() && r.nextBoolean();
        }
        return isLeaf;
      }

      private void buildChildren(final ObservableList<TreeItem<String>> children) {
        final ObservableList<TreeItem<String>> taskChildren = FXCollections.observableArrayList();

        if (!this.isLeaf()) {
          Task<Integer> task = new Task<Integer>() {
            @Override
            protected Integer call() throws Exception {
              int i;
              int max = r.nextInt(500);
              for (i = 0; i <= max; i++) {
                taskChildren.addAll(new TreeItem[]{createNode("#" + r.nextInt())});
              }
              return i;
            }
          };

          task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override public void handle(WorkerStateEvent workerStateEvent) {
              children.setAll(taskChildren);
            }
          });
          new Thread(task).start();
        }
      }
    };
  }

}

更新 - 说明解决方案为何有效

上面的解决方案无法收到 aConcurrentModificationException因为所ObservableLists涉及的内容永远不会同时修改。

  • taskChildren集合仅在任务的用户线程上修改,并且
  • 主动附加到场景图的树项的子项仅在任务的 JavaFX 应用程序线程上进行修改。

这是通过以下项目确保的:

  1. taskChildren.addAll在任务的call方法中调用。
  2. 在用户线程上调用任务的调用方法。
  3. children.setAll(taskChildren)在 JavaFX 应用程序线程上调用。
  4. JavaFX 系统确保onSucceeded在 JavaFX 应用程序线程上调用任务的事件处理程序。
  5. 任务完成后,将不再向给定taskChildren列表添加子代,并且该列表永远不会被修改。
  6. 对于执行的每项任务taskChildren,都会创建一个新列表,因此taskChildren永远不会在任务之间共享给定列表。
  7. 每次对树进行修改时,都会创建一个新任务。
  8. 任务语义使得给定任务只能运行一次并且永远不会重新启动。
  9. 附加到活动场景图的 TreeItem 的子项仅在任务成功完成并停止处理后在 JavaFX 应用程序线程上进行修改。

为什么不应该在addAll(List)呼叫的那一刻准确地执行TreeItem呼叫updateExpandedDescendentCount()

updateExpandedDescendentCount()不是公共TreeItemapi 的一部分 - 它是一种内部实现方法,TreeView与解决此问题无关。


更新部分更新

JavaFX 任务文档有一个“返回部分结果的任务”的解决方案。使用类似的东西,您应该能够解决“应用程序在开始时无法使用,因为必须等待'buildChildren'线程完成才能看到任何节点。”的问题。这是因为部分结果解决方案将允许结果以小批量的方式从构建器任务线程“流式传输”回 FX 应用程序线程。

这种解决方案在实现上比我上面提供的解决方案更复杂,但应该允许您拥有一个符合您要求的响应式 UI。与往常一样,在处理并发情况时,需要格外小心,以确保共享数据不会同时发生突变,从而导致您在原始帖子中遇到的潜在竞争条件。

于 2013-04-22T02:40:05.280 回答
-1

这很简单:当客户端代码开始迭代集合时,您将继续更新集合。

删除线程或确保它在迭代器创建之前完成,或进行某种同步。

于 2013-04-20T21:26:45.767 回答