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

如何实现任务间带相对延迟的ScheduledExecutorService?

问题:实现串行延迟执行的批量任务调度

需求:使用ScheduledExecutorService保证同一时间仅一个任务处于活跃状态,前一个任务完成后,下一个任务以任意时长的相对延迟开始执行。

示例场景:模拟10到1的倒计时,期望每个任务间隔1秒执行(实际场景延迟不固定,因此无法使用scheduleAtFixedRate)。原代码如下:

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public void startCountdown() {
  for (int i = 10; i > 0; i--) {
    int countdownNumber = i;
    scheduler.schedule(() -> {
      System.out.println(countdownNumber);
    }, 1, TimeUnit.SECONDS);
  }
}

问题:运行时所有数字会一次性打印,而非间隔1秒依次输出。已知计算绝对延迟的方法,但过于繁琐,询问Java中是否存在机制可一次性批量提交任务,且任务间等待延迟结束后再执行?


解决方案

方法1:递归链式调度

这是最简洁直接的方案,无需批量提交,让每个任务执行完成后主动调度下一个任务,天然保证串行和相对延迟,适配任意动态延迟场景。

改造后的倒计时代码:

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public void startCountdown() {
    scheduleCountdown(10);
}

private void scheduleCountdown(int number) {
    if (number <= 0) {
        return;
    }
    scheduler.schedule(() -> {
        System.out.println(number);
        // 当前任务完成后,调度下一个任务,这里延迟1秒(实际可替换为动态计算的延迟值)
        scheduleCountdown(number - 1);
    }, 1, TimeUnit.SECONDS);
}

方法2:CompletableFuture链式调用

如果需要预先定义所有任务和对应延迟,可以结合CompletableFuture的异步链式能力,实现批量任务的串行延迟执行。

示例代码:

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public void startCountdown() {
    // 预先构建任务列表:每个元素包含倒计时数字和对应的延迟时长(这里统一1秒,实际可自定义)
    List<Map.Entry<Integer, Long>> tasks = IntStream.rangeClosed(1, 10)
            .mapToObj(i -> new AbstractMap.SimpleEntry<>(11 - i, 1L))
            .collect(Collectors.toList());

    // 初始化一个已完成的Future作为链式起点
    CompletableFuture<Void> current = CompletableFuture.completedFuture(null);

    for (var task : tasks) {
        int num = task.getKey();
        long delay = task.getValue();
        // 绑定链式逻辑:前一个任务完成后,延迟指定时间执行当前任务
        current = current.thenRunAsync(() -> {
            // 提交当前任务到调度器并等待完成,保证串行
            try {
                CompletableFuture.runAsync(() -> System.out.println(num), scheduler).get();
            } catch (InterruptedException | ExecutionException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }, CompletableFuture.delayedExecutor(delay, TimeUnit.SECONDS, scheduler));
    }

    // 等待所有任务执行完毕(可选)
    current.join();
}

为什么原代码失效?

原代码中所有任务是同时提交的,且每个任务的延迟都设为1秒,因此它们会在提交后的第1秒同时触发执行,导致一次性打印所有数字。而我们需要的是前一个任务完成后才开始计算延迟,所以必须让任务的调度依赖于前一个任务的完成事件。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:55:15