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

ScheduledExecutorService任务延迟执行问题(低CPU内存占用)

解决ScheduledExecutorService任务调度间隔远超预期的问题

看起来你遇到了一个典型的调度线程阻塞+线程池配置不当的问题,我帮你分析一下原因并给出修正方案:

问题根源分析

1. 错误使用Thread.run()而非Thread.start()

在你的MyTask.run()方法中,这段代码是关键错误:

new Thread(() -> performAction()).run();

Thread.run()并不会启动新线程,它只是在**当前线程(也就是调度器的线程)**中直接执行Runnable的逻辑。而你的performAction()里有Thread.sleep(1000),这会阻塞调度器线程整整1秒。

因为你是在任务执行完(包括这1秒阻塞)之后,才调用scheduler.schedule(this, 2, SECONDS)调度下一次执行,所以两次任务的实际间隔变成了:任务执行耗时(1秒) + 调度延迟(2秒)= 3秒左右,这完全符合你看到的输出结果。

2. 单线程调度池的排队瓶颈

你用Executors.newScheduledThreadPool(1)创建了单线程的调度池,所有任务的run()方法都要在这同一个线程里排队执行。当你创建10个任务时,第一次执行所有任务的run()就需要10×1秒=10秒,后续每个任务的调度也会因为线程排队进一步延迟,导致间隔越来越不准。

3. 手动调度的方式不够可靠

你通过在run()中手动调用schedule()实现重复执行,其实ScheduledExecutorService已经提供了更专业的scheduleAtFixedRate和scheduleWithFixedDelay方法,前者以上一次任务开始时间为基准计算间隔,后者以上一次任务结束时间为基准,更适合定期任务场景。

修正后的代码示例

调度器类(SchedulersCreator)

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

class SchedulersCreator { 
    // 根据任务数量调整线程池大小,避免任务排队
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10); 
    public SchedulersCreator(int tasksAmount, int repeatCount) { 
        // 修正循环条件:原代码<=会多创建一个任务
        for (int taskId = 0; taskId < tasksAmount; taskId++) { 
            MyTask task = new MyTask(repeatCount, taskId); 
            // 使用scheduleAtFixedRate:初始延迟0秒,每隔2秒执行一次
            Future<?> future = scheduler.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);
            task.setFuture(future);
        } 
    } 
    public static void main(String[] args) { 
        System.out.println("Program started"); 
        SchedulersCreator scheduler = new SchedulersCreator(10, 10); 
        System.out.println("All tasks created & started"); 
    } 
}

任务类(MyTask)

import java.util.concurrent.Future;

class MyTask implements Runnable { 
    private int executesTimesLeft; 
    private final int id; 
    private long lastExecution = 0; 
    private Future<?> future; // 用于任务执行完毕后取消调度

    public MyTask(int executes, int id) { 
        this.executesTimesLeft = executes; 
        this.id = id; 
    } 

    public void setFuture(Future<?> future) {
        this.future = future;
    }

    private void performAction() { 
        long before = System.currentTimeMillis(); 
        long timeSinceLast = before - lastExecution; 
        lastExecution = before; 

        // 模拟业务计算
        try { 
            Thread.sleep(1000); 
        } catch (InterruptedException ex) { 
            // 正确处理中断,避免中断状态丢失
            Thread.currentThread().interrupt(); 
            return;
        } 

        long after = System.currentTimeMillis(); 
        long duration = after - before; 
        System.out.printf("Time since prev execution:\t%dms\tTask %d: %d executions left; current duration\t%dms%n", 
                timeSinceLast, id, executesTimesLeft, duration); 

        executesTimesLeft--;
        // 执行次数用完,取消任务调度
        if (executesTimesLeft <= 0 && future != null) {
            future.cancel(false);
        }
    } 

    @Override 
    public void run() { 
        if (executesTimesLeft > 0) { 
            performAction(); 
        } 
    } 
}

关键修正点说明

  1. 移除多余线程创建:直接利用调度器线程池执行任务逻辑,不需要手动创建线程(如果业务逻辑真的需要异步,再考虑额外线程池,但这里没必要)。
  2. 调整线程池大小:设置线程池大小为10(和任务数量一致),避免任务排队等待执行。
  3. 使用scheduleAtFixedRate:确保任务以固定间隔执行,不受任务执行耗时的影响(如果希望任务结束后再等2秒执行,改用scheduleWithFixedDelay)。
  4. 正确控制任务执行次数:通过Future在任务执行完指定次数后取消调度,避免无效执行。
  5. 修正循环条件:原代码的taskId <= tasksAmount会多创建一个任务,改为<符合预期。

这样修改后,任务的执行间隔会接近你预期的2秒,同时避免了线程阻塞和排队的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:37:47