一、闹钟式编程:为什么需要定时任务?

每天早上7点自动执行的晨跑提醒,双十一0点的秒杀活动触发,财务报表的月末自动生成...这些场景都在使用定时任务。Spring Boot为我们提供了@Scheduled注解和Quartz两种武器,就像单兵作战装备和集团军作战系统,应对不同的战场需求。

二、快速上手:@Scheduled原住民方案

2.1 启动定时宇宙的开关

要让@Scheduled生效,先启动定时任务宇宙:

@SpringBootApplication
@EnableScheduling // 宇宙大爆炸开关
public class TaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskApplication.class, args);
    }
}

2.2 三种基础时间配置法

@Component
public class BasicTask {

    // 每次执行结束后间隔3秒(看门狗模式)
    @Scheduled(fixedDelay = 3000)
    public void feedDog() {
        System.out.println("🐶 喂狗完成:" + new Date());
    }

    // 无视执行时间,每5秒准时出发(地铁运行模式)
    @Scheduled(fixedRate = 5000)
    public void subwayRun() {
        System.out.println("🚇 地铁发车:" + new Date());
    }

    // 凌晨执行数据归档(天文台模式)
    @Scheduled(cron = "0 0 3 * * ?")
    public void dataArchive() {
        System.out.println("📦 数据归档:" + new Date());
    }
}

2.3 高精度时间配置示例

# application.properties
# 动态配置间隔时间(单位:毫秒)
task.interval=7000
@Scheduled(fixedRateString = "${task.interval}")
public void dynamicTask() {
    System.out.println("🎯 动态间隔任务:" + new Date());
}

2.4 并发控制实战

默认情况下,定时任务都是单线程执行:

@Configuration
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);    // 核心线程数
        executor.setMaxPoolSize(10);    // 最大应急线程数
        executor.setQueueCapacity(20);  // 等候区座位数
        executor.setThreadNamePrefix("task-"); // 工牌前缀
        return executor;
    }
}

@Component
@EnableAsync
public class ConcurrentTask {

    @Async("taskExecutor")  // 启用异步执行
    @Scheduled(fixedRate = 2000)
    public void multiThreadTask() {
        System.out.println(Thread.currentThread().getName() 
            + " 正在工作:" + new Date());
    }
}

三、专业级方案:Quartz集团军作战

3.1 Quartz的三大核心装备

  • Job:具体作战任务(如发送邮件)
  • Trigger:作战时间表(如每周一8点)
  • Scheduler:作战指挥部

3.2 Spring Boot整合Quartz

// 系统管理员的记事本
public class EmailJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        System.out.println("📧 发送日报邮件:" + new Date());
    }
}

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail emailJobDetail() {
        return JobBuilder.newJob(EmailJob.class)
                .withIdentity("dailyEmail")
                .storeDurably() // 持久化任务
                .build();
    }

    @Bean
    public Trigger emailTrigger() {
        CronScheduleBuilder schedule = CronScheduleBuilder
                .cronSchedule("0 0 9 ? * MON-FRI"); // 工作日上午9点

        return TriggerBuilder.newTrigger()
                .forJob(emailJobDetail())
                .withIdentity("emailTrigger")
                .withSchedule(schedule)
                .build();
    }
}

3.3 动态任务调度示例

@Service
public class DynamicTaskService {

    @Autowired
    private Scheduler scheduler;

    // 新增动态任务(如根据配置创建)
    public void addDynamicTask(String jobName, String cron) 
        throws SchedulerException {
        
        JobDetail job = JobBuilder.newJob(DynamicJob.class)
                .withIdentity(jobName)
                .usingJobData("config", "自定义参数")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName + "Trigger")
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();

        scheduler.scheduleJob(job, trigger);
    }

    // 暂停任务(如临时维护)
    public void pauseJob(String jobName) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName);
        scheduler.pauseJob(jobKey);
    }
}

public class DynamicJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        JobDataMap data = context.getJobDetail().getJobDataMap();
        System.out.println("🎛️ 执行动态任务:" + data.getString("config"));
    }
}

四、战场景观:应用场景对比

4.1 @Scheduled最佳作战地形

  • 简单定时需求(每天数据备份)
  • 固定间隔执行(每10分钟检测服务状态)
  • 单机环境应用
  • 不需要持久化的任务

4.2 Quartz集团军适用战区

  • 需要动态调整执行策略
  • 分布式集群环境
  • 需要失败重试机制
  • 任务需要持久化存储
  • 复杂调度策略(如月最后一天)

五、装备性能评估:技术优缺点

5.1 @Scheduled突击队优缺点

优点:

  • 快速集成(5分钟部署)
  • 零配置启动
  • 开发成本极低

缺点:

  • 单线程执行默认模式
  • 不支持动态调整
  • 缺乏持久化机制
  • 分布式环境下容易重复执行

5.2 Quartz集团军优劣势

优势:

  • 支持集群部署
  • 完善的失败处理机制
  • 可视化调度控制(配合管理界面)
  • 灵活的任务持久化方案

劣势:

  • 学习曲线较高
  • 需要额外数据库支持
  • 配置复杂度指数级上升

六、行军备忘录:关键注意事项

  1. 线程池优化:Quartz默认线程数是10,高并发场景要调整
# application.properties
spring.quartz.properties.org.quartz.threadPool.threadCount=20
  1. 分布式环境锁机制:使用Redis实现分布式锁
@Scheduled(cron = "0 */5 * * * ?")
public void distributedTask() {
    String lockKey = "taskLock";
    if(redisLock.tryLock(lockKey, 300)) {
        try {
            // 执行任务
        } finally {
            redisLock.unlock(lockKey);
        }
    }
}
  1. 异常熔断策略:为任务增加异常重试
public class RetryJob extends QuartzJobBean {
    private static final int MAX_RETRY = 3;

    @Override
    protected void executeInternal(JobExecutionContext context) {
        int retryCount = 0;
        while(retryCount < MAX_RETRY) {
            try {
                // 业务逻辑
                break;
            } catch (Exception e) {
                retryCount++;
            }
        }
    }
}

七、参谋部总结

@Scheduled像瑞士军刀,适合快速解决简单问题;Quartz如同专业工具包,能够构建企业级调度系统。选择时需要考虑:是否需要动态调整?执行环境是否分布式?任务是否需要持久化?现在尝试在你的项目中同时使用两种方案:用@Scheduled处理简单心跳检测,用Quartz管理订单超时取消,体验它们的完美配合!