从 SolverEventListener 修改解决方案时出现 Optaplanner ConcurrentModificationException

Optaplanner ConcurrentModificationException when modifying Solution from SolverEventListener

我正在使用集成到 JavaFX GUI 中的 OptaPlanner 求解器解决调度问题,该 GUI 会根据每次改进进行更新。 由于将其连接到 GUI,此异常经常发生在构造启发式完成后。

Exception in thread "Thread-6" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.cloneCollection(FieldAccessingSolutionCloner.java:297)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.process(FieldAccessingSolutionCloner.java:280)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.processQueue(FieldAccessingSolutionCloner.java:273)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.cloneSolution(FieldAccessingSolutionCloner.java:206)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner.cloneSolution(FieldAccessingSolutionCloner.java:72)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.cloneSolution(AbstractScoreDirector.java:142)
    at org.optaplanner.core.impl.solver.scope.DefaultSolverScope.setWorkingSolutionFromBestSolution(DefaultSolverScope.java:198)
    at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:217)
    at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)
    at no.scheduling.shifts.solver.ShiftOptaPlanner.solve(ShiftOptaPlanner.java:124)
    at application.ScheduleSolver.run(ScheduleSolver.java:69)
    at java.lang.Thread.run(Thread.java:745) 

我的 GUI 代码确实修改了收到的解决方案,因为它包含的项目必须以排序的方式显示给用户。 当我对侦听器的 bestSolutionChanged() 方法提供的解决方案使用防御性克隆时,问题就消失了。 我在这里做错了什么,或者事件侦听器有时可能会获得 OptaPlanner 使用的有线实例而不是防御性克隆? 我真的很想避免克隆步骤,因为我目前可用的克隆功能有限且速度慢。

编辑: 通过进一步调试,我想我能够在 OptaPlanner 源代码(6.4.0 Final)中查明问题 完成构造启发式阶段后,BestSolutionRecaller 中的 updateBestSolution() 方法将相同的解决方案实例放入 fireBestSolutionChanged() 中,因为它存储在 solverScope 中:

 public void updateBestSolution(DefaultSolverScope solverScope, Solution solution, int uninitializedVariableCount) {
        (...)
        solverScope.setBestSolution(solution);
        (...)
        solverEventSupport.fireBestSolutionChanged(solution, uninitializedVariableCount);
    } 

这个完全相同的实例然后在 setWorkingSolutionFromBestSolution 中进行克隆,同时我的 GUI 线程正在对其中的事件列表进行排序,并且在 [=18] 中迭代事件列表时抛出异常=]. 猜猜问题的解决方案是克隆 fireBestSolutionChanged() 的解决方案?

我是偶然发现了一个细微的错误还是做错了什么?谢谢。

在 bestSolutionEvent 期间不要进行深度克隆(= 防御性复制)。由于性能原因,optaplanner-core 也没有这样做(该代码中没有错误,但 planner 确实在 BestSolutionRecaller 中进行了计划克隆(!=深度克隆))。

相反,在您的 ProblemFactChange 中进行深度克隆。

深度克隆不同于计划克隆(在文档中有解释)。 7.0.0.Beta2 文档包含对此的解释以进一步阐明这一点: