OptaPlanner切换ConstraintProvider后启发式构建阶段偶发NullPointerException问题排查求助
从你的错误栈和代码来看,这个偶发的NPE问题根源在于构造启发式算法执行过程中,部分Customer实例的previousStandstill还未被赋值(处于null状态),而你的distanceToPreviousStandstill约束直接调用了getDistanceFromPreviousStandstill()方法,该方法在previousStandstill为null时返回了null值,导致penalizeLong无法处理null的分数值。
为什么用EasyScoreCalculator时没问题?大概率是因为你在EasyScoreCalculator的实现中,对未分配的Customer(previousStandstill为null的情况)做了防御性处理,比如跳过计算或者默认返回0,而切换到ConstraintProvider后,约束流规则没有做对应的null检查。
具体解决方案
方案1:在约束中过滤未分配的Customer
修改distanceToPreviousStandstill约束,添加过滤条件,只处理已经分配了previousStandstill的Customer:
protected Constraint distanceToPreviousStandstill(ConstraintFactory factory) { return factory.from(Customer.class) // 过滤掉还未分配到路径中的Customer .filter(customer -> customer.getPreviousStandstill() != null) .penalizeLong("distanceToPreviousStandstill", BendableLongScore.ofSoft(BENDABLE_SCORE_HARD_LEVELS_SIZE, BENDABLE_SCORE_SOFT_LEVELS_SIZE, 3, 1), Customer::getDistanceFromPreviousStandstill); }
方案2:修改getDistanceFromPreviousStandstill()方法的null处理
在Customer类的该方法中,当previousStandstill为null时返回0L而非null:
public Long getDistanceFromPreviousStandstill() { if (previousStandstill == null) { // 未分配的客户,距离为0,不产生惩罚 return 0L; } // 原有的距离计算逻辑 // return calculateDistance(previousStandstill, this); }
方案3:检查OptaPlanner版本并升级
如果你的OptaPlanner版本较旧,可能存在多线程构造启发式下约束流的偶发bug。建议升级到最新的稳定版本(比如8.x系列的最新版),看看是否能解决这个偶发问题。
为什么问题是偶发的?
因为构造启发式算法是逐步构建解的,在多线程环境下(你用了MultiThreadedConstructionHeuristicDecider),不同线程处理的Customer实例所处的状态可能不同:有的已经完成分配,有的还处于待分配状态。只有当某个线程恰好处理到未分配的Customer时,才会触发NPE,所以问题无法稳定复现。
内容的提问来源于stack exchange,提问作者sindeaf

