在一个具体的时间节点实现任务。

  1. 单机定时任务
  2. 分布式定时任务:需要专门的调度框架,通过分布式锁、注册中心、任务分发机制等实现协调,解决重复执行
  3. 分布式框架定时任务

一.Timer+TimerTask:Timer就是一个while在循环执行run()

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();

        // 定义任务
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务,时间:" + System.currentTimeMillis());
            }
        };

        // 延迟 2 秒后执行,每隔 3 秒执行一次
        timer.schedule(task, 2000, 3000);
    }
}
  • 同一个 Timer → 串行执行任务(单线程)。

  • 不同的 Timer → 各自独立线程,可以并行。

❌ 缺点

单线程:所有任务都在一个线程中运行,如果一个任务耗时过长,会阻塞后续任务。

异常风险:如果某个任务抛出未捕获异常,整个 Timer 线程会挂掉,所有任务停止。

不适合并发场景:无法充分利用多核 CPU。

二.ScheduledExecutorService:延迟队列 + 线程池

类层次结构
ScheduledExecutorService(接口)
ScheduledThreadPoolExecutor(实现类)继承自 ThreadPoolExecutor
内部基于 延迟队列 (DelayedWorkQueue) 管理任务
👉 所以它的本质就是一个 带调度能力的线程池。
基于 线程池,可以并行调度多个任务,避免了单线程阻塞的问题。

  • schedule:往延迟队列里放一个任务,线程池线程在 delay 到时后取出并执行一次。

  • scheduleAtFixedRate:计算下一次的绝对触发时间 = 上次计划时间 + period,到了时间就往队列丢任务,慢了就会堆积/追赶。

  • scheduleWithFixedDelay:等任务真正执行完,再计算下一次触发时间 = 当前完成时间 + delay,所以节奏稳定,不会堆积。
    import java.util.concurrent.*;

public class ScheduledExecutorExample {
public static void main(String[] args) {
// 创建一个带 2 个线程的调度线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

    System.out.println("程序开始时间:" + System.currentTimeMillis());

    // 1. 一次性延迟任务
    scheduler.schedule(() -> {
        System.out.println("一次性延迟任务执行时间:" + System.currentTimeMillis());
    }, 3, TimeUnit.SECONDS);

    // 2. 固定速率任务(任务耗时 2 秒,周期 1 秒)
    scheduler.scheduleAtFixedRate(() -> {
        long start = System.currentTimeMillis();
        System.out.println("固定速率任务开始:" + start);
        try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
        long end = System.currentTimeMillis();
        System.out.println("固定速率任务结束:" + end);
    }, 1, 1, TimeUnit.SECONDS);

    // 3. 固定延迟任务(任务耗时 2 秒,延迟 1 秒)
    scheduler.scheduleWithFixedDelay(() -> {
        long start = System.currentTimeMillis();
        System.out.println("固定延迟任务开始:" + start);
        try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
        long end = System.currentTimeMillis();
        System.out.println("固定延迟任务结束:" + end);
    }, 1, 1, TimeUnit.SECONDS);

    // 为了演示,这里设置 15 秒后关闭线程池
    scheduler.schedule(() -> {
        System.out.println("关闭调度器:" + System.currentTimeMillis());
        scheduler.shutdown();
    }, 15, TimeUnit.SECONDS);
}

}

一次性任务:Future.get() 可以拿到结果(Callable 返回值,Runnable 返回 null)。
周期任务:返回的 ScheduledFuture 主要用于 取消任务,不能用来拿结果。

所以:
想要结果 → 用一次性任务 + Callable。
想要周期性调度 → 返回值一般只用来 cancel。
一次性:

ScheduledFuture<?> schedule(Callable<V> callable, long delay, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
如果你用 Callable,任务会返回一个结果;
如果是 Runnable,返回值是 null。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<Integer> future = scheduler.schedule(() -> {
    return 42;
}, 2, TimeUnit.SECONDS);
System.out.println("任务结果: " + future.get()); // 2秒后打印 42



ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
    System.out.println("周期任务执行:" + System.currentTimeMillis());
}, 1, 1, TimeUnit.SECONDS);

// 运行 5 秒后取消任务
scheduler.schedule(() -> {
    System.out.println("取消周期任务");
    future.cancel(false); // false 表示不打断正在执行的任务,等当前任务自己结束后不再执行新任务了
}, 5, TimeUnit.SECONDS);

ScheduledExecutorService 的核心确实是 “延迟队列 + 线程池” 的组合
任务提交:任务被包装为具有延迟属性的ScheduledFutureTask,然后放入延迟队列中,队列会按照执行时间排序。
线程池的工作线程会从延迟队列中获取到已到期的任务(如果没有已到期任务,线程会阻塞等待,直到被唤醒)
当线程池没有工作线程时,已到期的延迟任务会加入到线程池的等待队列。
对于周期性任务,执行完毕后会重新计算下次执行时间然后放入延迟队列。

Spring的定时任务:Spring 的定时任务功能(@Scheduled 注解等)本质上是对 Java 原生 ScheduledExecutorService 的封装和扩展

@Scheduled 注解本身不直接支持分布式场景
在 Spring 配置类上添加@EnableScheduling注解,启用定时任务功能。
创建任务类,使用@Scheduled注解定义定时任务的执行规则。
线程池配置:


```java
@Configuration
@EnableScheduling // 启用定时任务
public class SchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        // 创建调度线程池实例
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        
        // 核心线程池大小(并发执行的任务数量)
        scheduler.setPoolSize(5);
        
        // 线程名称前缀(便于日志跟踪)
        scheduler.setThreadNamePrefix("scheduled-task-");
        
        // 任务执行超时时间(毫秒),超过此时间未执行的任务会被中断
        scheduler.setAwaitTerminationSeconds(60);
        
        // 当调度器关闭时,是否等待所有任务执行完成
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        
        // 任务拒绝策略(当线程池满时如何处理新任务)
        scheduler.setRejectedExecutionHandler((Runnable r, java.util.concurrent.ThreadPoolExecutor executor) -> {
            // 示例:记录任务拒绝日志
            System.err.println("任务被拒绝执行: " + r.toString() + ",当前线程池状态: " + executor.getActiveCount() + "活跃线程/" + executor.getPoolSize() + "总线程");
        });
        
        // 初始化线程池
        scheduler.initialize();
        return scheduler;
    }
}

配置TreadPoolTaskScheduler的Bean,然后会自动注册到spring中

@Configuration
@EnableScheduling
public class SchedulerConfig {
    // 第一个调度器
    @Bean(name = "scheduler1")
    public ThreadPoolTaskScheduler scheduler1() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(3);
        scheduler.setThreadNamePrefix("scheduler-1-");
        return scheduler;
    }

    // 第二个调度器
    @Bean(name = "scheduler2")
    public ThreadPoolTaskScheduler scheduler2() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(2);
        scheduler.setThreadNamePrefix("scheduler-2-");
        return scheduler;
    }
}

可以创建多个调度器,然后将任务加入到不同调度器。
当然,通常是增大单个调度器的线程大小

// 使用第一个调度器
@Scheduled(fixedRate = 1000, scheduler = "scheduler1")
public void task1() { ... }

// 使用第二个调度器
@Scheduled(fixedRate = 2000, scheduler = "scheduler2")
public void task2() { ... }

常用配置

@Component
public class ScheduledExamples {

    // 1. 固定速率执行:每5秒执行一次(以上一次开始时间计算)
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() {
        System.out.println("固定速率任务 - " + LocalDateTime.now());
    }

    // 2. 固定延迟执行:上一次完成后隔3秒执行
    @Scheduled(fixedDelay = 3000)
    public void fixedDelayTask() {
        System.out.println("固定延迟任务 - " + LocalDateTime.now());
    }

    // 3. 初始延迟+固定速率:启动后延迟2秒,之后每4秒执行
    @Scheduled(initialDelay = 2000, fixedRate = 4000)
    public void initialDelayTask() {
        System.out.println("初始延迟任务 - " + LocalDateTime.now());
    }

    // 4. Cron表达式:每天10:30执行
    @Scheduled(cron = "0 30 10 * *?")
    public void dailyCronTask() {
        System.out.println("每日10:30任务 - " + LocalDateTime.now());
    }

    // 5. Cron表达式:每30秒执行一次
    @Scheduled(cron = "0/30 * * * *?")
    public void intervalCronTask() {
        System.out.println("每30秒任务 - " + LocalDateTime.now());
    }

    // 6. 时间单位配置(Spring 5.3+):每2分钟执行一次
    @Scheduled(fixedRate = 2, timeUnit = TimeUnit.MINUTES)
    public void timeUnitTask() {
        System.out.println("每2分钟任务 - " + LocalDateTime.now());
    }

    // 7. 指定调度器(需提前配置名为"specialScheduler"的线程池)
    @Scheduled(fixedRate = 10000, scheduler = "specialScheduler")
    public void specifiedSchedulerTask() {
        System.out.println("指定调度器任务 - " + LocalDateTime.now());
    }
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐