Timefold:Shadow Variable与克隆问题——三类模型双向关联咨询
三类模型场景下OptaPlanner Shadow Variable的正确用法及并发异常解决
一、核心映射逻辑梳理
你的模型里,SelectionNode是规划实体,核心规划变量为SelectionVariant;SelectionTeam需要反向维护关联的SelectionNode列表——这是典型的双向关联影子变量场景,但因为中间多了SelectionVariant一层中转,不能直接套用两类实体的简单绑定逻辑。
正确的触发路径应该是:
- 当
SelectionNode的SelectionVariant变量变更时,触发变量监听器 - 监听器先找到旧
SelectionVariant下的所有SelectionTeam,将当前SelectionNode从它们的关联列表中移除 - 再找到新
SelectionVariant下的所有SelectionTeam,将当前SelectionNode添加到它们的关联列表中
二、ConcurrentModificationException 解决核心:让集合支持解决方案克隆
你遇到的并发异常确实是因为OptaPlanner克隆解决方案时,默认不会深度克隆普通集合(比如ArrayList),导致原方案与克隆方案共享同一集合实例,后续操作触发并发修改检查。解决分两步:
1. 给SelectionTeam的关联列表做克隆支持
不要用普通ArrayList,改用OptaPlanner兼容的可克隆集合,或者手动实现深度克隆:
- 方案一(OptaPlanner 8+推荐):使用
PlanningList,它会自动处理克隆逻辑:public class SelectionTeam { private List<SelectionNode> selectionNodeList = new PlanningList<>(); // 其他字段、getter/setter } - 方案二(旧版本兼容):在
SelectionTeam的clone()方法中手动深度克隆集合:public class SelectionTeam implements Cloneable { private List<SelectionNode> selectionNodeList = new ArrayList<>(); @Override public SelectionTeam clone() { try { SelectionTeam cloned = (SelectionTeam) super.clone(); // 深度克隆列表,SelectionNode作为规划实体需实现自身克隆逻辑 cloned.selectionNodeList = new ArrayList<>(); for (SelectionNode node : selectionNodeList) { cloned.selectionNodeList.add(node.clone()); } return cloned; } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } // 其他字段、getter/setter }
2. 正确配置Shadow Variable与VariableListener
第一步:给SelectionTeam的列表字段标注Shadow Variable
明确源变量为SelectionNode的selectionVariant,并关联自定义监听器:
public class SelectionTeam { @ShadowVariable( sourceVariableName = "selectionVariant", variableListenerClass = SelectionNodeToTeamListener.class, elementClass = SelectionNode.class ) private List<SelectionNode> selectionNodeList = new PlanningList<>(); // 提供安全的集合操作方法,避免直接修改原集合 public void addSelectionNode(SelectionNode node) { this.selectionNodeList.add(node); } public void removeSelectionNode(SelectionNode node) { this.selectionNodeList.remove(node); } // getter/setter }
第二步:实现VariableListener处理双向关联同步
监听器需处理SelectionNode变量变更时,同步更新对应SelectionTeam的列表:
public class SelectionNodeToTeamListener implements VariableListener<SelectionNode> { @Override public void beforeVariableChanged(ScoreDirector scoreDirector, SelectionNode selectionNode) { // 清理旧Variant关联的Team列表 SelectionVariant oldVariant = selectionNode.getSelectionVariant(); if (oldVariant != null) { for (SelectionTeam team : oldVariant.getSelectionTeamList()) { scoreDirector.beforeVariableChanged(team, "selectionNodeList"); team.removeSelectionNode(selectionNode); scoreDirector.afterVariableChanged(team, "selectionNodeList"); } } } @Override public void afterVariableChanged(ScoreDirector scoreDirector, SelectionNode selectionNode) { // 绑定新Variant关联的Team列表 SelectionVariant newVariant = selectionNode.getSelectionVariant(); if (newVariant != null) { for (SelectionTeam team : newVariant.getSelectionTeamList()) { scoreDirector.beforeVariableChanged(team, "selectionNodeList"); team.addSelectionNode(selectionNode); scoreDirector.afterVariableChanged(team, "selectionNodeList"); } } } // 以下方法留空实现即可 @Override public void beforeEntityAdded(ScoreDirector scoreDirector, SelectionNode entity) {} @Override public void afterEntityAdded(ScoreDirector scoreDirector, SelectionNode entity) {} @Override public void beforeEntityRemoved(ScoreDirector scoreDirector, SelectionNode entity) {} @Override public void afterEntityRemoved(ScoreDirector scoreDirector, SelectionNode entity) {} }
三、关键注意事项
- 操作影子变量集合时,必须用
ScoreDirector的beforeVariableChanged和afterVariableChanged包裹,让OptaPlanner追踪变化,避免分数计算错误 - 禁止在监听器外直接修改
SelectionTeam的selectionNodeList,所有变更必须通过SelectionNode的变量变化触发 - 如果
SelectionVariant是固定配置(非规划实体/变量),无需额外配置,仅处理SelectionNode的变量变更即可
内容的提问来源于stack exchange,提问作者ptrushin
相关产品推荐
相关产品推荐

