JavaFX:ConcurrentModificationException在单独的线程中在TreeView中添加TreeItem对象时

JavaFX:ConcurrentModificationException在单独的线程中在TreeView中添加TreeItem对象时,treeview,javafx,concurrentmodification,Treeview,Javafx,Concurrentmodification,我有以下代码 public void start(Stage primaryStage) { BorderPane border_pane = new BorderPane(); TreeView tree = addTreeView(); //TreeView on the LEFT border_pane.setLeft(tree); /* more stuf

我有以下代码

public void start(Stage primaryStage) {        
    BorderPane border_pane = new BorderPane();

    TreeView tree = addTreeView();                                          //TreeView on the LEFT
    border_pane.setLeft(tree);

    /* more stuff added to the border_pane here... */

    Scene scene = new Scene(border_pane, 900, 700);
    scene.setFill(Color.GHOSTWHITE);

    primaryStage.setTitle("PlugControl v0.1e");
    primaryStage.setScene(scene);
    primaryStage.show();
}

public static void main(String[] args) {
    launch(args);
}
new Thread(task).start();
task.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{ 
   @Override 
   public void handle(WorkerStateEvent workerStateEvent) { 
      for(Plug p1 : listOfPlugs)
      {
         PlugTreeItem<String> pti = new PlugTreeItem(pl.getSIHUid().getValue() 
         + " " + pl.getLocation() + " " + pl.getAppliance(),
         new ImageView(new Image(
         getClass().getResourceAsStream("graphics/smiley.png"))), pl); 
         treeItemRoot.getChildren().add(pti);
      }
}
addTreeView
是一个从SQL数据库中读取数据并基于该数据添加约35个
TreeItem
s的函数。将
TreeItem
s添加到
treeItemRoot
是在单独的线程中完成的注意:treeItemRoot在主类中声明,在此之前为null

public TreeView addTreeView() {                                             //Our treeView is positioned on the LEFT
        treeItemRoot = new PlugTreeItem<>("Active Plugs", new ImageView(new Image(getClass().getResourceAsStream("graphics/plugicon.png"))), new Plug());          //Root of the tree, contains a dummy Plug object.
        selectedTreeItem = treeItemRoot;

        treeItemRoot.setExpanded(true);                                         //always expand it

        selectedTreeItem.getPlugItem()
                .getSIHUid().addListener(new ChangeListener<String>() {
                    @Override
                    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue
                    ) {
                        System.err.println("changed " + oldValue + "->" + newValue);
                    }
                }
                );

        TreeView<String> treeView = new TreeView<>(treeItemRoot);               //Build the tree with our root node.

        final Task task;
        task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {

                //=========== SQL STUFF BEGINS HERE ============================
                Statement sta = null;
                ResultSet result_set = null;
                Connection conn = null;

                try {
                    try {
                        System.err.println("Loading JDBC driver...");
                        Class.forName("com.mysql.jdbc.Driver");
                        System.err.println("Driver loaded!");
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("Cannot find JDBC driver in the classpath!", e);
                    }

                    System.err.println("Connecting to database...");
                    conn = DriverManager.getConnection("[DB link here]", "[username]", "[password]");           //Username is PlugControl, pw is woof
                    System.err.println("Connected to Database!");

                    sta = conn.createStatement();
                    String sql_query = "SELECT * FROM pwnodes INNER JOIN pwcomports ON pwnodes.NetworkID = pwcomports.NetworkID WHERE pwnodes.connection = 'on' ORDER BY pwnodes.Location";

                    result_set = sta.executeQuery(sql_query);
                    System.err.println("SQL query successfuly executed!");

                    int count = 0;
                    while (result_set.next()) {
                        Plug pl = null;                                         //MARKER: We might need to do switch (result_set.getString("Server")) for SIHU1 and SIHU2.
                        count++;
                        pl = new Plug(result_set.getString("SIHUid"), result_set.getString("sensorID"), result_set.getString("Location"), result_set.getString("Appliance"), result_set.getString("Type"), result_set.getString("connection"), result_set.getString("Server"), result_set.getString("ServerIP"));
                        PlugTreeItem<String> pti = new PlugTreeItem(pl.getSIHUid().getValue() + " " + pl.getLocation() + " " + pl.getAppliance(), new ImageView(new Image(getClass().getResourceAsStream("graphics/smiley.png"))), pl); //icon does not work in children
                        treeItemRoot.getChildren().add(pti);                    //CONCURRENCY ERRORS HERE
                    }
                    System.err.println("ALERT SQL QUERY RESULTS: " + count);

                } catch (SQLException e) //linked try clause @ line 50
                {
                    throw new RuntimeException("Cannot connect the database!", e);
                } finally {   //  Time to wrap things up, by closing all open SQL procs. 
                    try {
                        if (sta != null) {
                            sta.close();
                        }
                        if (result_set != null) {
                            result_set.close();
                        }
                        if (conn != null) {
                            System.err.println("Closing the connection.");
                            conn.close();
                        }
                    } catch (SQLException e) //We might as well ignore this, but just in case.
                    {
                        throw new RuntimeException("Error while closing up statement, result set and connection!", e);
                    }
                }

                //============== SQL STUFF ENDS HERE ===========================
                System.err.println("Finished");
                return null;
            }
        };

        new Thread(task).start();                                               //Run the task!

        treeView.getSelectionModel()
                .selectedItemProperty().addListener(new ChangeListener() {
                    @Override
                    public void changed(ObservableValue observable, Object oldValue, Object newValue
                    ) {
                        selectedTreeItem = (PlugTreeItem<String>) newValue;
                        System.err.println("DEBUG: Selection plug SIHUid: " + selectedTreeItem.getPlugItem().print());           //MARKER: REMOVE
                        updateTextFields();                                             //Update TextAreas.
                        if (!"DUMMY".equals(selectedTreeItem.getPlugItem().getSIHUid().getValue())) {
                            buttonOn.setDisable(false);
                            buttonOff.setDisable(false);
                        } else {
                            buttonOn.setDisable(true);
                            buttonOff.setDisable(true);
                        }
                    }
                }
                );
        return treeView;
    }
伙计们,在处理这个问题上有什么帮助/建议吗?这个异常并没有将我指向代码中的某个位置,到目前为止,我正在研究预感

编辑:作为参考,
PlugTreeItem
只是一个treeiItem,它还带有一个
Plug
,Plug是我的一个类,它包含一些字符串值。没什么特别的。
公共类PlugTreeItem扩展了TreeItem{\*code*\}
我看不到您在javafx应用程序线程上同步,这是来自另一个线程时所必需的

我建议您在
while循环
中创建一个
列表
,而不是创建单个对象并将其添加到树中,因为
javafx控件上的所有操作都必须在
javafx线程上完成,而不是在任务线程上

在线程体外部创建一个列表

List<Plug> listOfPlugs = new ArrayList<Plug>();
稍后,在启动线程后,可以生成以下代码

public void start(Stage primaryStage) {        
    BorderPane border_pane = new BorderPane();

    TreeView tree = addTreeView();                                          //TreeView on the LEFT
    border_pane.setLeft(tree);

    /* more stuff added to the border_pane here... */

    Scene scene = new Scene(border_pane, 900, 700);
    scene.setFill(Color.GHOSTWHITE);

    primaryStage.setTitle("PlugControl v0.1e");
    primaryStage.setScene(scene);
    primaryStage.show();
}

public static void main(String[] args) {
    launch(args);
}
new Thread(task).start();
task.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{ 
   @Override 
   public void handle(WorkerStateEvent workerStateEvent) { 
      for(Plug p1 : listOfPlugs)
      {
         PlugTreeItem<String> pti = new PlugTreeItem(pl.getSIHUid().getValue() 
         + " " + pl.getLocation() + " " + pl.getAppliance(),
         new ImageView(new Image(
         getClass().getResourceAsStream("graphics/smiley.png"))), pl); 
         treeItemRoot.getChildren().add(pti);
      }
}
新线程(任务).start();
task.setOnSucceeded(新的EventHandler()
{ 
@凌驾
公共无效句柄(WorkerStateEvent WorkerStateEvent){
用于(插头p1:插头列表)
{
PlugTreeItem pti=新的PlugTreeItem(pl.getSIHUid().getValue())
+“”+pl.getLocation()+“”+pl.getAppliance(),
新图像视图(新图像(
getClass().getResourceAsStream(“graphics/smiley.png”)),pl);
treeItemRoot.getChildren().add(pti);
}
}

whats
plug
PlugTreeItem
?@ItachiUchiha查看我的更新问题。感谢您的评论:)您的答案虽然正确,但有一个重大缺陷。由于线程使用SQL查询,因此可以在for()之后轻松完成下面的循环已开始并完成将子循环放入
treeItemRoot
。解决方案是将for循环放入
task.setOnSucceeded(new EventHandler(){@Override public void handle(WorkerStateEvent WorkerStateEvent){\*for loop here*\})
确保它只在任务完成后运行。我从另一个答案中找到了这一点,并结合您的答案构建了我的解决方案:是的,非常正确!我匆忙错过了这一点。我将编辑并添加它以供将来参考!!!既然您编辑了它,并且答案现在是正确的,我可以继续接受它作为答案。