JavaFX: ConcurrentModificationException while addi

2019-08-28 20:32发布

问题:

I have the following code

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);
}

With addTreeView being a function that reads data off an SQL DB and adds around ~35 TreeItems, based on that data. The addition of TreeItems to treeItemRoot is done in a seperate thread. NOTE: treeItemRoot is declared in the main class, and is null before here.

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;
    }

Once every 5-6 runs I receive a ConcurrentModificationException because I think the Thread task() doesn't manage to finish before addTreeView's returned TreeView is added to the border_pane, and maybe it begins iterating through it while it's still having items added to it?

Executing C:\Users\74\Documents\NetBeansProjects\PlugControl_v0.5\dist\run1559674105\PlugControl.jar using platform C:\Program Files\Java\jdk1.7.0_25\jre/bin/java
Loading JDBC driver...
Driver loaded!
Connecting to database...
Connected to Database!
SQL query successfuly executed!
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)
    at javafx.scene.control.TreeItem.getExpandedDescendentCount(TreeItem.java:777)
    at javafx.scene.control.TreeView.getExpandedDescendantCount(TreeView.java:864)
    at javafx.scene.control.TreeView.updateTreeItemCount(TreeView.java:873)
    at javafx.scene.control.TreeView.impl_getTreeItemCount(TreeView.java:533)
    at com.sun.javafx.scene.control.skin.TreeViewSkin.getItemCount(TreeViewSkin.java:207)
    at com.sun.javafx.scene.control.skin.TreeViewSkin.updateItemCount(TreeViewSkin.java:220)
    at com.sun.javafx.scene.control.skin.TreeViewSkin.handleControlPropertyChanged(TreeViewSkin.java:135)
    at com.sun.javafx.scene.control.skin.SkinBase$3.changed(SkinBase.java:282)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:107)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:196)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.IntegerPropertyBase.fireValueChangedEvent(IntegerPropertyBase.java:123)
    at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:130)
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:163)
    at javafx.scene.control.TreeView.setTreeItemCount(TreeView.java:515)
    at javafx.scene.control.TreeView.updateTreeItemCount(TreeView.java:876)
    at javafx.scene.control.TreeView.impl_getTreeItemCount(TreeView.java:533)
    at javafx.scene.control.TreeCell.updateItem(TreeCell.java:391)
    at javafx.scene.control.TreeCell.access$000(TreeCell.java:67)
    at javafx.scene.control.TreeCell$1.invalidated(TreeCell.java:95)
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:155)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.ReadOnlyIntegerWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:195)
    at javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:161)
    at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:130)
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:163)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:112)
    at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1596)
    at com.sun.javafx.scene.control.skin.VirtualFlow.addLeadingCells(VirtualFlow.java:1049)
    at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1005)
    at com.sun.javafx.scene.control.skin.VirtualFlow.setCellCount(VirtualFlow.java:206)
    at com.sun.javafx.scene.control.skin.TreeViewSkin.updateItemCount(TreeViewSkin.java:225)
    at com.sun.javafx.scene.control.skin.TreeViewSkin.handleControlPropertyChanged(TreeViewSkin.java:135)
    at com.sun.javafx.scene.control.skin.SkinBase$3.changed(SkinBase.java:282)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:107)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:196)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.IntegerPropertyBase.fireValueChangedEvent(IntegerPropertyBase.java:123)
    at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:130)
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:163)
    at javafx.scene.control.TreeView.setTreeItemCount(TreeView.java:515)
    at javafx.scene.control.TreeView.updateTreeItemCount(TreeView.java:876)
    at javafx.scene.control.TreeView.impl_getTreeItemCount(TreeView.java:533)
    at javafx.scene.control.TreeCell.updateItem(TreeCell.java:391)
    at javafx.scene.control.TreeCell.access$000(TreeCell.java:67)
    at javafx.scene.control.TreeCell$1.invalidated(TreeCell.java:95)
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:155)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.ReadOnlyIntegerWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:195)
    at javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:161)
    at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:130)
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:163)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:112)
    at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1596)
    at com.sun.javafx.scene.control.skin.VirtualFlow.getCell(VirtualFlow.java:1500)
    at com.sun.javafx.scene.control.skin.VirtualFlow.getCellLength(VirtualFlow.java:1523)
    at com.sun.javafx.scene.control.skin.VirtualFlow$3.call(VirtualFlow.java:478)
    at com.sun.javafx.scene.control.skin.VirtualFlow$3.call(VirtualFlow.java:476)
    at com.sun.javafx.scene.control.skin.PositionMapper.computeViewportOffset(PositionMapper.java:143)
    at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1001)
    at javafx.scene.Parent.layout(Parent.java:1018)
    at javafx.scene.Parent.layout(Parent.java:1028)
    at javafx.scene.Parent.layout(Parent.java:1028)
    at javafx.scene.Parent.layout(Parent.java:1028)
    at javafx.scene.Scene.layoutDirtyRoots(Scene.java:516)
    at javafx.scene.Scene.doLayoutPass(Scene.java:487)
    at javafx.scene.Scene.access$3900(Scene.java:170)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2203)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:363)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:460)
    at com.sun.javafx.tk.quantum.QuantumToolkit$9.run(QuantumToolkit.java:329)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.access$100(WinApplication.java:29)
    at com.sun.glass.ui.win.WinApplication$3$1.run(WinApplication.java:73)
    at java.lang.Thread.run(Thread.java:724)
ALERT SQL QUERY RESULTS: 35
Closing the connection.
Finished

Any help/advice on dealing with the issue, guys? The exception doesn't point me towards a place in my code, and so far I'm working on hunches.}

EDIT: For reference, PlugTreeItem is just a TreeItem<> that also carries a Plug with it, Plug being a class of mine that holds a few String values. Nothing special. public class PlugTreeItem<T> extends TreeItem{ \* code *\}

回答1:

I would suggest you to make a List<Plug> inside your while loop rather than making individual object and adding it to the tree, because all operations on javafx controls must be done on javafx thread and not on task thread !

Create a list outside the Thread body

List<Plug> listOfPlugs = new ArrayList<Plug>();

Then, in the while loop, you can write

int count = 0;
while (result_set.next()) {
    Plug pl = null;                                        
    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"));
    listOfPlugs.add(p1);                  
}

Later, after you start the thread you can make the following code

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);
      }
}


回答2:

I don't see you syncing on the javafx application thread which is required when coming from another one