【问题标题】:JavaFX Chart Series ConcurrentModificationExceptionJavaFX 图表系列 ConcurrentModificationException
【发布时间】:2016-05-02 17:49:04
【问题描述】:

我正在 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 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 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();

}

【问题讨论】:

  • 您在一个任务中调用addData(...),这意味着该方法不是在FX Thread 上调用,而是在另一个Thread 上调用。尝试移动代码,将 UI 更新为 task.setOnSucceeded,例如
  • 但这只会更新一次 UI。我有 20 次迭代,我想为每个迭代进行实时更新
  • 为什么要使用并发和任务?该过程是否需要花费大量时间?如果是这样,每次迭代需要多少时间和整个过程,什么方法需要时间,为什么需要时间,是在做很多复杂的计算并消耗 CPU 还是在等待 I/O 或它通过线程睡眠调用人为暂停?这些问题的答案应该可以帮助您在基于任务或时间线的方法之间做出选择。
  • 好的,所以 .nextGeneration() 方法实际上并不会立即运行,但我不需要暂停线程即可。
  • 而且我不知道我应该等多久才能完成该方法。这取决于输入的大小...

标签: java javafx task


【解决方案1】:

根据经验,每当您修改 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);
    }
});

【讨论】:

  • 我完全按照你说的做了,这会返回完全奇怪的结果。最大适应度甚至可能低于平均水平。顺便说一句,在 runLater 之外运行更新会产生不错的结果,但在第 60 - 200 代时总是会失败
  • 我在 runLater 中添加了我添加到图表中的结果的 System.out.println。似乎他们甚至没有接近图表显示的内容
  • 尝试将整个 for 循环放在 platform.runlater 调用中。您注意到的结果可能来自您在 JavaFX 应用程序线程等待标记时正在使用的更新数据的任务。
  • 将整个循环放在 runLater 中(条件是其余代码保持不变)仅在 for 完成迭代时更新(仅在最后一次迭代时)也许我应该摆脱任务并仅运行 runLater?
  • 而且整个窗口在函数运行时冻结
【解决方案2】:

尝试在Timeline 动画中更新图表。

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

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

时间线的好处是,您可以指定更新之间的持续时间,我在您的代码中看不到这一点

【讨论】:

  • 更新(即使持续时间为 1 毫秒)一点也不顺畅。如何让它顺利?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 2017-10-21
  • 1970-01-01
  • 2012-08-12
  • 1970-01-01
相关资源
最近更新 更多