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

如何避免ScheduledThreadPoolExecutor的scheduleAtFixedRate任务堆积?

解决ScheduledThreadPoolExecutor任务堆积导致的连续执行问题

问题代码

public static void main(String[] args){

    ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(5);

    AtomicInteger i = new AtomicInteger(0);
    Runnable runnable = () -> {

        if(i.get() < 5){
            try{ Thread.sleep(2_000); }catch(Exception ignored){ }
        }else if(i.get() >= 15){
            System.exit(0);
        }

        System.out.println(i.getAndIncrement() + " - " + stpe.getQueue().size() + " - " + LocalDateTime.now());

    };

    ScheduledFuture<?> sf = stpe.scheduleAtFixedRate(new Thread(runnable, "Testing"), 0, 1_000, TimeUnit.MILLISECONDS);

}

执行输出

0 - 0 - 2023-03-27T13:40:04.865501800
 1 - 0 - 2023-03-27T13:40:06.886657500
 2 - 0 - 2023-03-27T13:40:08.890066
 3 - 0 - 2023-03-27T13:40:10.894186200
 4 - 0 - 2023-03-27T13:40:12.898171
 5 - 0 - 2023-03-27T13:40:12.899077100 <---
 6 - 0 - 2023-03-27T13:40:12.899077100 <---
 7 - 0 - 2023-03-27T13:40:12.900068700 <---
 8 - 0 - 2023-03-27T13:40:12.900068700 <---
 9 - 0 - 2023-03-27T13:40:12.900068700 <---
10 - 0 - 2023-03-27T13:40:12.901073800 <---
11 - 0 - 2023-03-27T13:40:13.857069500
12 - 0 - 2023-03-27T13:40:14.852331500
13 - 0 - 2023-03-27T13:40:15.860971200
14 - 0 - 2023-03-27T13:40:16.865873300

问题分析

scheduleAtFixedRate会按固定周期尝试提交任务,若当前任务未执行完成,后续任务会被放入等待队列。当长耗时任务执行完毕后,队列中堆积的任务会被连续执行,破坏预期的执行速率。

需求是:保持「任务结束到下一个任务启动的间隔为周期减去任务执行时间」的逻辑(即任务超时则立即启动下一个),但禁止任务堆积,后续任务需按「上一个任务结束时间+周期」的节奏执行,而非一次性清空队列。

解决方案

方法1:手动递归调度(推荐)

放弃scheduleAtFixedRate,改为每次任务执行完毕后,手动计算下一次调度的延迟时间,单独调度下一次任务。这种方式不会产生任务堆积,完全符合预期节奏。

修改后的代码示例:

public static void main(String[] args) {
    ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(5);
    AtomicInteger i = new AtomicInteger(0);
    long period = 1000; // 固定周期1秒

    Runnable task = new Runnable() {
        @Override
        public void run() {
            long startTime = System.currentTimeMillis();
            try {
                // 模拟任务执行逻辑
                if (i.get() < 5) {
                    Thread.sleep(2000);
                } else if (i.get() >= 15) {
                    System.exit(0);
                }
                System.out.println(i.getAndIncrement() + " - " + stpe.getQueue().size() + " - " + LocalDateTime.now());
            } catch (Exception ignored) {
            } finally {
                // 计算下次调度延迟:周期 - 任务执行耗时,若为负则立即执行(延迟0)
                long executionTime = System.currentTimeMillis() - startTime;
                long delay = Math.max(0, period - executionTime);
                // 递归调度下一次任务
                stpe.schedule(this, delay, TimeUnit.MILLISECONDS);
            }
        }
    };

    // 首次调度
    stpe.schedule(task, 0, TimeUnit.MILLISECONDS);
}

方法2:清理等待队列(不推荐)

如果坚持使用scheduleAtFixedRate,可以在每次任务执行时清理队列中堆积的重复任务,但逻辑复杂且存在线程安全风险:

  • 给任务添加唯一标识;
  • 每次任务执行前,加锁遍历stpe.getQueue(),移除队列中尚未执行的同标识任务;
  • 该操作可能干扰调度器的正常运行,稳定性难以保证。

说明

方法1的核心是每次只调度下一次任务,由任务自身控制后续执行节奏,既实现了「超时则立即启动下一个」的逻辑,又彻底避免了任务堆积。执行后,任务5到10会按1秒间隔依次执行,而非瞬间连续输出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:57:03