You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 13:17:53