我的代码在后台任务中创建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();
}
}
};
}
}
答案 0 :(得分:8)
目前的答案没有帮助。关键是你必须在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/
答案 1 :(得分:3)
您无法直接修改任何影响活动节点的任何内容以及与JavaFX应用程序线程以外的任何线程相关的场景图(以及包含TreeView项)的数据。
有关示例任务(返回ObservableList或部分结果的任务)的详细信息,请参阅Task文档,这将有助于您解决问题。您需要在新的ObservableList中的Task中创建新的TreeItem,然后,一旦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();
}
}
};
}
}
更新 - 解决方案工作原理的说明
上述解决方案无法收到ConcurrentModificationException
,因为所涉及的ObservableLists
永远不会同时修改。
taskChildren
个集合以下项目确保了这一点:
taskChildren.addAll
在任务的call
方法中调用。children.setAll(taskChildren)
。onSucceeded
事件处理程序。taskChildren
列表中,并且永远不会修改该列表。 taskChildren
列表,因此任务之间永远不会共享给定的taskChildren
列表。为什么不应在
addAll(List)
调用TreeItem
的时刻准确执行updateExpandedDescendentCount()
来电?
updateExpandedDescendentCount()
不属于公开TreeItem
API - 它是TreeView
的内部实施方法,与解决此问题无关。
更新部分更新
JavaFX Task documentation有一个“返回部分结果的任务”的解决方案。使用类似的东西,您应该能够解决“应用程序在开始时无法使用的问题,因为必须等待'buildChildren'线程完成才能看到任何节点。”这是因为部分结果解决方案将允许结果从构建器任务线程小批量“流式传输”回FX应用程序线程。
这种解决方案的实现比我上面提供的解决方案更复杂,但应该允许您拥有符合您要求的响应式UI。与往常一样,在处理并发情况时,需要格外小心以确保共享数据不会同时发生变化,从而导致您在原始帖子中遇到的潜在竞争条件。
答案 2 :(得分:0)
这很简单:当客户端代码开始迭代时,您将继续更新集合。
删除线程或确保在创建迭代器之前完成,或进行某种同步。