JavaFX图表系列ConcurrentModificationException

JavaFX图表系列ConcurrentModificationException,java,javafx,task,Java,Javafx,Task,我正在JavaFX上构建一个遗传算法,我想要一个图表来更新每一代人的平均和最大适应度。 我有一个按钮,可以生成20代,每次迭代都会更新图表,还有进度条 代码过去更简单,没有所有的重复和引用。这些只是关于如何避免例外的想法。但什么都没用 我的意思是,我甚至不迭代或删除一些东西。这只是关于添加 我得到一个例外: Exception in thread "JavaFX Application Thread" java.util.ConcurrentModificationException a

我正在JavaFX上构建一个遗传算法,我想要一个图表来更新每一代人的平均和最大适应度。 我有一个按钮,可以生成20代,每次迭代都会更新图表,还有进度条

代码过去更简单,没有所有的重复和引用。这些只是关于如何避免例外的想法。但什么都没用

我的意思是,我甚至不迭代或删除一些东西。这只是关于添加

我得到一个例外:

Exception in thread "JavaFX Application Thread" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042)
    at javafx.scene.chart.AreaChart.layoutPlotChildren(AreaChart.java:451)
    at javafx.scene.chart.XYChart.layoutChartChildren(XYChart.java:731)
    at javafx.scene.chart.Chart$1.layoutChildren(Chart.java:94)
    at javafx.scene.Parent.layout(Parent.java:1079)
    at javafx.scene.Parent.layout(Parent.java:1085)
    at javafx.scene.Parent.layout(Parent.java:1085)
    at javafx.scene.Parent.layout(Parent.java:1085)
    at javafx.scene.Scene.doLayoutPass(Scene.java:552)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$31(Toolkit.java:348)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:347)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:374)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$405(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
这是准备工作

public void prepareChart() {
    final NumberAxis xAxis = new NumberAxis();
    final NumberAxis yAxis = new NumberAxis();
    final AreaChart<Number, Number> graph = new AreaChart<Number, Number>(xAxis, yAxis);
    graph.setCreateSymbols(false);
    graph.setTitle("Fitness - Generations relation");
    graph.getXAxis().setLabel("Generations");
    graph.getYAxis().setLabel("Fitness");

    final Series<Number, Number> averageFitness = new XYChart.Series<Number, Number>();
    averageFitness.setName("Average fitness");

    final Series<Number, Number> maximumFitness = new XYChart.Series<Number, Number>();
    maximumFitness.setName("Maximum fitness");
    graph.getData().addAll(averageFitness, maximumFitness);
    graphBox.getChildren().add(graph);
    graphRef = graph;
}
public void prepareChart(){
最终数字axis xAxis=新数字axis();
最终数字axis yAxis=新数字axis();
最终面积图=新面积图(xAxis,yAxis);
graph.setCreateSymbols(false);
图.setTitle(“适合度-世代关系”);
graph.getXAxis().setLabel(“代”);
graph.getYAxis().setLabel(“适合度”);
最终系列averageFitness=新的XYChart.Series();
averageFitness.setName(“Average fitness”);
最终系列maximumFitness=新的XYChart.Series();
maximumFitness.setName(“最大适合度”);
graph.getData().addAll(averageFitness,maximumFitness);
graphBox.getChildren().add(图形);
graphhref=图形;
}
下面是我如何添加数据的方法

public void addData(double avgFitness, double maxFitness) {
    final Series<Number, Number> averageFitness = graphRef.getData().get(0);
    final Series<Number, Number> maximumFitness = graphRef.getData().get(1);
    averageFitness.getData().add(new XYChart.Data<Number, Number>(generation, avgFitness));
    maximumFitness.getData().add(new XYChart.Data<Number, Number>(generation, maxFitness));
    generation++;

}
public void addData(双avgFitness,双maxFitness){
最终系列averageFitness=graphRef.getData().get(0);
最终系列maximumFitness=graphRef.getData().get(1);
averageFitness.getData().add(新的XYChart.Data(生成,avgFitness));
maximumFitness.getData().add(新的XYChart.Data(生成,maxFitness));
生成++;
}
以下是单击按钮时发生的情况

public void twentyGenerationsPressed() {

    Task<Void> task = new Task<Void>() {
        @Override
        public Void call() throws InterruptedException {
        for (int i = 1; i <= 20; i++) {
            disableButtons();
            solver.nextGeneration();
            updateProgress(i, 20);
            executor.execute(() -> {
            addData(solver.getAverage(), solver.getCurrentBest().getFitness());
            });

        }

        return null;
        }
    };
    task.setOnSucceeded(e -> {
        updateLabels();
        enableButtons();
    });

    progressBar.progressProperty().bind(task.progressProperty());

    Thread th = new Thread(task);
    th.setDaemon(true);
    th.start();

}
public void twentyGenerationsPressed(){
任务=新任务(){
@凌驾
public Void call()引发InterruptedException{
对于(int i=1;i{
addData(solver.getAverage()、solver.getCurrentBest().getFitness());
});
}
返回null;
}
};
task.setonsucceed(e->{
updateLabels();
启用按钮();
});
progressBar.progressProperty().bind(task.progressProperty());
线程th=新线程(任务);
th.setDaemon(true);
th.start();
}

根据经验,任何时候修改JavaFX对象的内容,都应该在JavaFX应用程序线程上执行

替换此项:

executor.execute(() -> {
    addData(solver.getAverage(), solver.getCurrentBest().getFitness());
});
为此:

final double avg = solver.getAverage();
final double best = solver.getCurrentBest().getFitness();
Platform.runLater(new Runnable() {
    @Override
    public void run() {
        addData(avg, best);
    }
});

尝试在
时间线
动画中更新图形

   Timeline timeline = new Timeline();
   timeline.getKeyFrames().add(new KeyFrame(Duration.millis(100), evt -> addData(...)));
   timeline.setCycleCount(20);

   button.setOnAction(e -> timeline.play());

时间线的好处是,您可以指定更新之间的持续时间,这在您在任务中调用
addData(…)
的代码中是看不到的,这意味着该方法不是在
FX线程上调用的,而是在不同的
线程上调用的。尝试移动代码,这会将UI更新为
任务。例如,setonSucceed
,但这只会更新UI一次。我有20个迭代,我希望每个迭代都有一个实时更新。为什么要使用并发和任务?这个过程需要花费很多时间吗?如果是这样的话,每次迭代和整个过程需要多少时间,需要时间的方法是什么,为什么需要时间,它是在做大量复杂的计算并消耗CPU,还是在等待I/O,还是通过线程休眠调用人工暂停?这些问题的答案应该可以帮助您在基于任务或时间线的方法之间进行选择。好吧,因此方法.nextGeneration()实际上不会在某个时刻运行,但我认为我不需要暂停线程来实现这一点。我不知道该等多久方法才能完成。这取决于输入的大小……我完全按照你说的做了,这会返回非常奇怪的结果。最大适应度甚至可能低于平均水平。顺便说一句,在runLater中运行更新会得到很好的结果,但在第60-200I代的某个地方总是失败。我在runLater中添加了一个System.out.println作为我添加到图表中的结果。看起来它们甚至不接近于图形showsTry将整个for循环放入platform.runlater调用。您注意到的结果可能来自于任务更新您正在使用的数据,而JavaFX应用程序线程等待标记。将整个循环放入runLater(条件是其余代码保持不变)仅在for完成其迭代时(仅在最后一次迭代时)才更新也许我应该放弃这个任务,只运行runLater?而且整个窗口在函数运行时冻结。更新(即使持续时间为1毫秒)一点也不顺利。我如何使它光滑?