ScheduledExecutorService任务延迟执行问题(低CPU内存占用)
看起来你遇到了一个典型的调度线程阻塞+线程池配置不当的问题,我帮你分析一下原因并给出修正方案:
问题根源分析
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(); } } }
关键修正点说明
- 移除多余线程创建:直接利用调度器线程池执行任务逻辑,不需要手动创建线程(如果业务逻辑真的需要异步,再考虑额外线程池,但这里没必要)。
- 调整线程池大小:设置线程池大小为10(和任务数量一致),避免任务排队等待执行。
- 使用
scheduleAtFixedRate:确保任务以固定间隔执行,不受任务执行耗时的影响(如果希望任务结束后再等2秒执行,改用scheduleWithFixedDelay)。 - 正确控制任务执行次数:通过
Future在任务执行完指定次数后取消调度,避免无效执行。 - 修正循环条件:原代码的
taskId <= tasksAmount会多创建一个任务,改为<符合预期。
这样修改后,任务的执行间隔会接近你预期的2秒,同时避免了线程阻塞和排队的问题。
内容的提问来源于stack exchange,提问作者Wsl_F

