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

OptaPlanner:SolverManager::solveAndListen是否存在同步/锁Bug?

OptaPlanner v8.22.1.Final:terminateEarly返回后finalBestSolutionConsumer未执行完成的问题

问题背景

TL;DR:默认实现DefaultSolverListener未对finalBestSolutionConsumer做正确同步,或者我误解了这个Consumer的用途?这是预期行为吗?

近期测试日志出现警告,显示最后当前最优解与最终最优解不匹配。原因是调用terminateEarly后,方法直接返回,并未等待finalBestSolutionConsumer线程执行完毕。虽然可通过中间解获取最后当前最优解,但最终最优解未及时保存的情况不符合预期。

代码场景

启动优化的调用代码:

this.getSolverManager()
    .solveAndListen(solutionID,
                    this::findCurrentBest,
                    this::saveCurrentBest,
                    this::saveFinalBest,
                    this::handleException);

saveFinalBest实现(非耗时操作):

this.finalBestConsumer.accept(finalBest); // 默认无操作
this.finalBestSolutionsMap.put(finalBest.getId(), finalBest);

后续执行逻辑:

// 日志记录
this.getSolverManager().terminateEarly(solutionID);
// 日志记录

final var finalBest = this.findFinalBest(solutionID);
final var crrntBest = this.findCurrentBest(solutionID);
final var best = ObjectUtils.max(finalBest, crrntBest);

if (!Objects.equals(finalBest, crrntBest)) {
    // 抛出警告
}

return best;

发现terminateEarly返回后,saveFinalBest尚未执行完成,就已经调用findFinalBest读取数据,导致结果不一致。

原因分析

OptaPlanner的terminateEarly方法仅负责发送求解终止信号,不会阻塞等待finalBestSolutionConsumer的执行完成。DefaultSolverListener中处理最终最优解的逻辑属于求解线程的收尾流程,是异步执行的,因此terminateEarly返回时,回调可能还未执行完毕。

解决方案

方案1:手动同步等待回调完成

通过同步工具(如CountDownLatch)标记saveFinalBest的执行状态,在terminateEarly后等待回调完成再读取数据:

// 用线程安全的Map存储每个solutionID对应的等待锁
private final Map<Long, CountDownLatch> terminationWaiters = new ConcurrentHashMap<>();

// 启动求解时初始化锁
CountDownLatch waitLatch = new CountDownLatch(1);
terminationWaiters.put(solutionID, waitLatch);

this.getSolverManager()
    .solveAndListen(solutionID,
                    this::findCurrentBest,
                    this::saveCurrentBest,
                    finalBest -> {
                        this.finalBestConsumer.accept(finalBest);
                        this.finalBestSolutionsMap.put(finalBest.getId(), finalBest);
                        // 标记最终解保存完成
                        waitLatch.countDown();
                    },
                    this::handleException);

// 触发终止
this.getSolverManager().terminateEarly(solutionID);
// 等待最终解保存完成
try {
    waitLatch.await();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    // 处理中断异常
}
terminationWaiters.remove(solutionID);

// 后续读取逻辑
final var finalBest = this.findFinalBest(solutionID);
final var crrntBest = this.findCurrentBest(solutionID);
// ... 其余逻辑

方案2:在回调内触发后续逻辑

如果后续逻辑依赖最终最优解,直接将这部分逻辑放在saveFinalBest中执行,避免跨线程的同步问题:

this.getSolverManager()
    .solveAndListen(solutionID,
                    this::findCurrentBest,
                    this::saveCurrentBest,
                    finalBest -> {
                        this.finalBestConsumer.accept(finalBest);
                        this.finalBestSolutionsMap.put(finalBest.getId(), finalBest);
                        // 在这里执行原本需要依赖finalBest的逻辑
                        processFinalBestSolution(finalBest);
                    },
                    this::handleException);

注意事项

确保finalBestSolutionsMap是线程安全的(比如使用ConcurrentHashMap),避免多线程读写时出现数据不一致问题。

结论:这是预期行为吗?

这不属于Bug,是terminateEarly的设计行为——该方法仅负责触发求解终止,不负责等待所有回调执行完毕。finalBestSolutionConsumer的执行属于求解线程的收尾工作,若需要同步等待回调完成,需自行实现同步机制。

内容的提问来源于stack exchange,提问作者Ryan Moser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 17:39:31