1. 问题场景解析
"一个子任务要依赖两个父任务完成才能执行"这个需求在实际开发中非常常见。比如电商系统中的订单支付流程,可能需要同时满足"库存校验"和"风控审核"两个前置条件才能执行支付操作。这种多依赖关系在分布式系统、批处理任务、工作流引擎等场景下尤为普遍。
我最近在重构一个物流调度系统时就遇到了类似问题:车辆路径规划子任务必须等待"订单数据准备完成"和"路况信息更新完成"两个父任务都执行完毕才能启动。下面分享几种经过实战验证的解决方案。
2. 基础解决方案对比
2.1 轮询检查法
最朴素的实现方式是让子任务定期检查父任务状态:
while(true) { if (parent1.isDone() && parent2.isDone()) { executeChildTask(); break; } Thread.sleep(1000); // 每秒检查一次 }注意:这种方式会浪费CPU资源,且响应延迟取决于轮询间隔。适合对实时性要求不高的后台任务。
2.2 回调嵌套法
通过回调函数的嵌套来实现依赖:
parent1.execute(() -> { parent2.execute(() -> { childTask.execute(); }); });这种写法在JavaScript中很常见,但在Java中会导致"回调地狱"。当依赖层级变多时,代码可读性会急剧下降。
3. CompletableFuture 解决方案
3.1 核心API介绍
Java 8的CompletableFuture提供了更优雅的解决方案:
CompletableFuture<Void> parent1 = CompletableFuture.runAsync(...); CompletableFuture<Void> parent2 = CompletableFuture.runAsync(...); CompletableFuture.allOf(parent1, parent2) .thenRun(() -> { // 子任务逻辑 });allOf()方法会等待所有给定的Future完成,然后触发后续操作。相比轮询方案,这种实现没有资源浪费,且代码更加简洁。
3.2 超时控制增强版
实际项目中建议添加超时控制:
CompletableFuture.allOf(parent1, parent2) .orTimeout(30, TimeUnit.SECONDS) .exceptionally(ex -> { log.error("任务超时", ex); return null; }) .thenRun(this::executeChildTask);4. 生产环境注意事项
4.1 任务编排可视化
对于复杂依赖关系,建议使用有向无环图(DAG)来建模。可以使用Graphviz生成可视化图表:
digraph G { parent1 -> child parent2 -> child }这样在排查问题时可以快速理清任务关系。
4.2 分布式场景处理
在微服务架构中,父任务可能分布在不同的服务中。这时可以采用:
- 事件驱动架构:父任务完成后发布事件,子任务订阅相关事件
- 工作流引擎:如Camunda、Activiti等
- 数据库状态标记:使用数据库记录任务状态
5. 性能优化技巧
5.1 并行化父任务
确保父任务之间没有依赖时可以并行执行:
CompletableFuture<Void> parent1 = CompletableFuture.runAsync(...); CompletableFuture<Void> parent2 = CompletableFuture.runAsync(...); // 会自动并行执行5.2 线程池隔离
不同类型的任务应该使用不同的线程池,避免相互影响:
ExecutorService ioPool = Executors.newFixedThreadPool(10); ExecutorService cpuPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); CompletableFuture<Void> parent1 = CompletableFuture.runAsync(ioTask, ioPool); CompletableFuture<Void> parent2 = CompletableFuture.runAsync(cpuTask, cpuPool);6. 常见问题排查
6.1 任务卡死
现象:子任务一直不执行 排查步骤:
- 检查父任务是否有未捕获的异常
- 查看线程池是否已满
- 确认没有循环依赖
6.2 性能瓶颈
现象:整体执行时间过长 优化方向:
- 分析父任务是否可以进一步拆分并行
- 检查IO密集型任务是否使用了合适的线程池
- 考虑使用缓存减少重复计算
7. 设计模式应用
7.1 观察者模式
可以通过观察者模式实现松耦合的任务通知:
public class ParentTask { private List<ChildTask> observers = new ArrayList<>(); public void addObserver(ChildTask observer) { observers.add(observer); } protected void notifyObservers() { observers.forEach(ChildTask::onParentComplete); } }7.2 状态模式
对于复杂的状态转换,可以使用状态模式:
interface TaskState { void checkCanExecute(); } class WaitingState implements TaskState { public void checkCanExecute() { if (parent1.isDone() && parent2.isDone()) { context.setState(new ExecutingState()); } } }8. 测试方案设计
8.1 单元测试要点
测试多任务依赖时需要注意:
- 模拟父任务的不同完成顺序
- 测试父任务超时场景
- 验证异常传播机制
示例测试用例:
@Test void shouldExecuteChildAfterBothParentsComplete() { CompletableFuture<Void> parent1 = new CompletableFuture<>(); CompletableFuture<Void> parent2 = new CompletableFuture<>(); AtomicBoolean childExecuted = new AtomicBoolean(false); CompletableFuture.allOf(parent1, parent2) .thenRun(() -> childExecuted.set(true)); assertFalse(childExecuted.get()); parent1.complete(null); assertFalse(childExecuted.get()); parent2.complete(null); assertTrue(childExecuted.get()); }9. 扩展思考
9.1 动态依赖关系
某些场景下依赖关系可能是动态确定的。这时可以使用:
List<CompletableFuture<?>> dependencies = getDynamicDependencies(); CompletableFuture.allOf(dependencies.toArray(new CompletableFuture[0])) .thenRun(...);9.2 部分成功处理
有时我们可能希望部分父任务失败时仍执行子任务:
CompletableFuture<Void> parent1 = CompletableFuture.runAsync(...); CompletableFuture<Void> parent2 = CompletableFuture.runAsync(...); parent1.exceptionally(ex -> null); parent2.exceptionally(ex -> null); CompletableFuture.allOf(parent1, parent2) .thenRun(...);10. 其他语言实现
10.1 JavaScript实现
在Node.js中可以使用Promise:
Promise.all([parent1, parent2]) .then(() => childTask());10.2 Python实现
Python 3.5+提供了类似的语法:
async def main(): await asyncio.gather(parent1(), parent2()) child_task()11. 日志与监控
11.1 关键日志点
建议在以下位置添加日志:
- 每个父任务开始/完成时
- 子任务触发条件满足时
- 超时或异常发生时
11.2 监控指标
需要监控的关键指标:
- 父任务执行时间分布
- 子任务等待时间
- 任务依赖关系图的状态
12. 架构设计建议
对于企业级系统,建议:
- 使用专门的工作流引擎管理复杂依赖
- 实现任务持久化,支持断点续跑
- 设计任务优先级机制
- 提供任务可视化监控界面
我在实际项目中发现,当依赖关系超过3层时,就应该考虑引入工作流引擎了。最近使用Camunda实现的一个订单处理流程,包含了17个相互依赖的任务节点,通过BPMN可视化设计器,大大提高了可维护性。