从 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 文档包含对此的解释以进一步阐明这一点:
我正在使用集成到 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 文档包含对此的解释以进一步阐明这一点: