彻底搞懂 Java 中的定时任务看这一篇就够了前言什么是定时任务简单说就是**让程序在指定时间自动执行某个任务**。比如- 每天凌晨 3 点跑批处理- 每隔 5 分钟检查订单状态- 每周一早上 9 点发送周报- 每月 1 号生成财务报表这些场景都离不开定时任务。本文会详细介绍 Java 中实现定时任务的 **4 种主流方式**并配上完整的代码示例保证你看完就能上手用。---一、为什么需要定时任务在正式讲代码之前先说说定时任务能做什么了解清楚它的应用场景。定时任务的典型应用| 场景 | 说明 ||------|------|| **数据同步** | 定时从第三方 API 拉取数据到本地 || **报表生成** | 每天/每周/每月自动生成统计报表 || **缓存清理** | 定时清理过期缓存释放内存 || **订单处理** | 定时检查超时未支付订单自动取消 || **通知推送** | 定时推送消息、邮件、短信 || **数据备份** | 凌晨定时备份数据库 || **健康检查** | 定时检查服务是否存活 |看到了吧定时任务的用途非常广泛几乎每个业务系统都离不开它。二、Java 定时任务的 4 种实现方式接下来进入正题看看 Java 中怎么实现定时任务。Timer TimerTask最简单这是 Java 自带的最原始的方式代码简单适合简单场景。import java.util.Timer; import java.util.TimerTask; /** * 方式一使用 Timer 实现定时任务 * * 特点 * - JDK 自带无需额外依赖 * - 简单易用 * - 单线程任务会串行执行 * - 如果某个任务抛出异常Timer 会停止所有任务 */ public class TimerDemo { public static void main(String[] args) { // 创建一个 Timer 实例 Timer timer new Timer(); // 1. 延迟 3 秒后执行一次 timer.schedule(new TimerTask() { Override public void run() { System.out.println(任务执行了当前时间 System.currentTimeMillis()); } }, 3000); // 3000 毫秒 3 秒 // 2. 延迟 1 秒后开始每隔 2 秒执行一次无限次 timer.scheduleAtFixedRate(new TimerTask() { private int count 0; Override public void run() { count; System.out.println(第 count 次执行时间 System.currentTimeMillis()); } }, 1000, 2000); // 延迟 1 秒间隔 2 秒 // 注意如果想停止任务调用 timer.cancel() // timer.cancel(); } }**运行结果** 任务执行了当前时间1647834000000 第 1 次执行时间1647834001000 第 2 次执行时间1647834003000 第 3 次执行时间1647834005000schedule() vs scheduleAtFixedRate() 的区别- schedule()下一次任务开始时间 上一次任务结束时间 间隔- scheduleAtFixedRate()下一次任务开始时间 上一次任务开始时间 间隔固定频率简单说如果任务执行时间超过了间隔schedule 会追不上scheduleAtFixedRate 会追赶。---ScheduledExecutorService推荐使用这是“JDK 1.5”提供的更强大的方式**线程池**实现更稳定、更高效。import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 方式二使用 ScheduledExecutorService 实现定时任务 * * 特点 * - JDK 1.5 提供无需额外依赖 * - 多线程并行执行互不影响 * - 某个任务抛出异常不影响其他任务 * - 官方推荐使用 */ public class ScheduledExecutorDemo { public static void main(String[] args) { // 创建一个线程池核心线程数为 2 // 这里的 2 表示最多有 2 个线程同时执行定时任务 ScheduledExecutorService scheduler Executors.newScheduledThreadPool(2); // 任务 1延迟 3 秒后执行一次 scheduler.schedule(() - { System.out.println(任务1执行了延迟3秒); }, 3, TimeUnit.SECONDS); // 任务 2延迟 1 秒后开始每隔 2 秒执行一次无限次 // 这里用了 Lambda 表达式更简洁 scheduler.scheduleAtFixedRate(() - { System.out.println(任务2执行了时间 System.currentTimeMillis()); }, 1, 2, TimeUnit.SECONDS); // 任务 3延迟 5 秒后开始每隔 3 秒执行一次执行 5 次后停止 scheduler.scheduleWithFixedDelay(() - { System.out.println(任务3执行了); }, 5, 3, TimeUnit.SECONDS); // 关闭 scheduler通常在应用关闭时调用 // scheduler.shutdown(); } }scheduleAtFixedRate() vs scheduleWithFixedDelay()// scheduleAtFixedRate固定频率 // 上次开始时间 间隔 下次开始时间 // 适合任务执行时间 间隔时间的场景 // scheduleWithFixedDelay固定延迟 // 上次结束时间 间隔 下次开始时间 // 适合任务执行时间不确定或者任务之间需要隔离的场景Spring ScheduledSpring 用户的首选如果你使用 Spring Framework这个注解就是你的首选。配置简单注解驱动。import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * 方式三Spring Scheduled 定时任务 * * 使用步骤 * 1. 在 Spring Boot 主类上添加 EnableScheduling 注解 * 2. 在方法上添加 Scheduled 注解 * 3. 方法需要是无参数、无返回值的 */ Component public class SpringScheduledDemo { /** * 每隔 5 秒执行一次 * fixedRate从上一次开始时间计算间隔 */ Scheduled(fixedRate 5000) public void task1() { System.out.println(每5秒执行一次 - System.currentTimeMillis()); } /** * 每隔 3 秒执行一次 * fixedDelay从上一次结束时间计算间隔 */ Scheduled(fixedDelay 3000) public void task2() { System.out.println(每3秒执行一次延迟 - System.currentTimeMillis()); } /** * 初始延迟 10 秒后开始然后每隔 5 秒执行 */ Scheduled(initialDelay 10000, fixedRate 5000) public void task3() { System.out.println(延迟10秒后每5秒执行 - System.currentTimeMillis()); } /** * Cron 表达式每秒执行 * Cron 格式秒 分 时 日 月 周 [年] */ Scheduled(cron * * * * * ?) public void task4() { System.out.println(每秒执行一次 - System.currentTimeMillis()); } /** * Cron 表达式每天凌晨 3 点执行 */ Scheduled(cron 0 0 3 * * ?) public void task5() { System.out.println(每天凌晨3点执行); } /** * Cron 表达式每周一早上 9 点执行 */ Scheduled(cron 0 0 9 ? * MON) public void task6() { System.out.println(每周一早上9点执行); } /** * Cron 表达式每月的 1 号凌晨 0 点执行 */ Scheduled(cron 0 0 0 1 * ?) public void task7() { System.out.println(每月1号凌晨0点执行); } }Spring Boot 启动类配置import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; SpringBootApplication EnableScheduling // 开启定时任务功能 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }Scheduled 完整参数说明Scheduled( // 固定间隔毫秒从上次开始计时 fixedRate 1000, // 固定延迟毫秒从上次结束计时 fixedDelay 1000, // 初始延迟毫秒首次执行前等待时间 initialDelay 1000, // Cron 表达式和 fixedRate 等互斥 cron 0/5 * * * * ?, // 时区 zone Asia/Shanghai ) public void doTask() { // 任务逻辑 }常用 Cron 表达式示例| 表达式 | 含义 ||--------|------|| 0/5 * * * * ? | 每 5 秒 || 0 0/1 * * * ? | 每分钟 || 0 0 * * * ? | 每小时 || 0 0 0 * * ? | 每天凌晨 || 0 0 12 * * ? | 每天中午 12 点 || 0 0 10,14,16 * * ? | 每天 10、14、16 点 || 0 0/30 9-17 * * ? | 9 点到 17 点每 30 分钟 || 0 0 12 ? * WED | 每周三中午 12 点 || 0 0 12 1 * ? | 每月 1 号中午 12 点 || 0 15 10 L * ? | 每月最后一天上午 10:15 |Quartz企业级定时任务如果你需要**动态管理任务**、**集群部署**、**任务依赖**等复杂场景Quartz 是更好的选择。import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; /** * 方式四Quartz 定时任务框架 * * 特点 * - 功能最强大支持动态管理任务 * - 支持集群、分布式 * - 支持任务依赖 * - 需要引入额外依赖 */ public class QuartzDemo { public static void main(String[] args) throws SchedulerException { // 1. 创建调度器Scheduler Scheduler scheduler StdSchedulerFactory.getDefaultScheduler(); // 2. 定义任务Job JobDetail job JobBuilder.newJob(MyJob.class) .withIdentity(myJob, group1) // 任务名和组名 .usingJobData(message, Hello Quartz) // 传递参数 .build(); // 3. 定义触发器Trigger // 每隔 5 秒执行一次 Trigger trigger TriggerBuilder.newTrigger() .withIdentity(myTrigger, group1) .startNow() // 立即开始 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(5) // 间隔 5 秒 .repeatForever()) // 无限次执行 .build(); // 4. 将任务和触发器关联到调度器 scheduler.scheduleJob(job, trigger); // 5. 启动调度器 scheduler.start(); // 关闭调度器 // scheduler.shutdown(); } } /** * 自定义任务类 * 需要实现 Job 接口 */ public class MyJob implements Job { Override public void execute(JobExecutionContext context) throws JobExecutionException { // 获取传递的参数 JobDataMap dataMap context.getJobDetail().getJobDataMap(); String message dataMap.getString(message); // 执行业务逻辑 System.out.println(Quartz 任务执行了message message); System.out.println(执行时间 System.currentTimeMillis()); } }Quartz 在 Spring Boot 中的使用import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; /** * Quartz 配置类Spring Boot 整合 */ Configuration public class QuartzConfig { /** * 配置 JobDetail */ Bean public JobDetailFactoryBean myJobDetail() { JobDetailFactoryBean factory new JobDetailFactoryBean(); factory.setJobClass(MyQuartzJob.class); factory.setDurability(true); return factory; } /** * 配置 Trigger */ Bean public SimpleTriggerFactoryBean myTrigger(JobDetailFactoryBean myJobDetail) { SimpleTriggerFactoryBean factory new SimpleTriggerFactoryBean(); factory.setJobDetail(myJobDetail.getObject()); factory.setRepeatInterval(5000); // 间隔 5 秒 factory.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); return factory; } }三、4 种方式的对比| 特性 | Timer | ScheduledExecutorService | Spring Scheduled | Quartz ||--------|---------|--------------------------------------|----------------------------|-----------|| **使用难度** | ⭐ 简单 | ⭐ 简单 | ⭐ 简单 | ⭐⭐ 较复杂 || **依赖** | JDK 自带 | JDK 1.5 | Spring Framework | 需引入依赖 || **多线程** | ❌ 单线程 | ✅ 多线程 | ✅ 多线程 | ✅ 多线程 || **异常处理** | 会导致全部停止 | 不影响其他任务 | 不影响其他任务 | 不影响其他任务 || **动态管理** | ❌ 不支持 | ❌ 不支持 | ⚠️ 支持但不灵活 | ✅ 强大支持 || **集群/分布式** | ❌ 不支持 | ❌ 不支持 | ⚠️ 需额外配置 | ✅ 原生支持 || **适用场景** | 简单定时任务 | 较复杂定时任务 | Spring 项目日常使用 | 企业级复杂任务 |四、实际业务场景示例场景 1每天凌晨同步数据Scheduled(cron 0 0 2 * * ?) public void syncData() { log.info(开始同步数据...); try { // 1. 从第三方 API 获取数据 ListData dataList externalApi.fetchData(); // 2. 处理数据 ListData processed processor.process(dataList); // 3. 保存到数据库 repository.saveAll(processed); log.info(数据同步完成共处理 {} 条数据, processed.size()); } catch (Exception e) { log.error(数据同步失败, e); // 可以发送告警通知 notifyService.alert(数据同步失败 e.getMessage()); } }场景 2定期清理过期订单Scheduled(fixedDelay 300000) // 每 5 分钟执行一次 public void cleanExpiredOrders() { log.info(开始清理过期订单...); // 查询超过 30 分钟未支付的订单 ListOrder expiredOrders orderRepository .findByStatusAndCreateTimeBefore( OrderStatus.PENDING, LocalDateTime.now().minusMinutes(30) ); for (Order order : expiredOrders) { order.setStatus(OrderStatus.CANCELLED); order.setCancelReason(超时未支付自动取消); } orderRepository.saveAll(expiredOrders); log.info(已清理 {} 条过期订单, expiredOrders.size()); }场景 3心跳检测Scheduled(fixedRate 60000) // 每分钟检查一次 public void healthCheck() { log.info(开始健康检查...); // 检查各个服务是否可用 boolean apiOk checkService(user-api); boolean paymentOk checkService(payment-api); boolean orderOk checkService(order-api); if (!apiOk || !paymentOk || !orderOk) { // 发送告警 String msg String.format(服务异常: user-api%s, payment-api%s, order-api%s, apiOk ? OK : FAIL, paymentOk ? OK : FAIL, orderOk ? OK : FAIL); alertService.sendAlert(msg); } } private boolean checkService(String serviceName) { try { return healthEndpoint.check(serviceName).isHealthy(); } catch (Exception e) { log.warn(检查服务 {} 失败, serviceName, e); return false; } }五、注意事项1. 时区问题// 默认使用服务器时区注意 Cron 表达式的时区 Scheduled(cron 0 0 9 * * ?) // 服务器时区早上9点 // 指定时区 Scheduled(cron 0 0 9 * * ?, zone Asia/Shanghai) // 上海时区早上9点2. 任务执行时间超过间隔// 如果任务执行时间是 8 秒间隔是 5 秒 // schedule任务完成后等 5 秒再执行下一次 // scheduleAtFixedRate立即执行下一次会追赶 // 建议任务执行时间 间隔时间时使用 scheduleWithFixedDelay Scheduled(fixedDelay 5000) // 上次结束后等 5 秒3. 集群环境下的定时任务在多实例部署时如果不处理多个实例会同时执行同一个定时任务导致数据问题。解决方案1. **Quartz 集群**自带分布式锁2. **Spring Scheduled Redis 分布式锁**3. **数据库行锁**4. **分布式调度中间件**XXL-Job、ElasticJob4. 优雅关闭PreDestroy public void shutdown() { log.info(关闭定时任务调度器...); if (scheduler ! null) { scheduler.shutdown(); } }六、总结1. **简单场景**用 Timer 或 ScheduledExecutorService2. **Spring 项目**用 Scheduled 注解最方便3. **复杂企业级**用 Quartz功能最强大4. **集群环境**选 Quartz 或分布式调度框架定时任务是后端开发的基本功面试也经常问。建议大家**自己动手写一遍**跑跑代码感受一下不同方式的区别。纸上得来终觉浅绝知此事要躬行。如果觉得有帮助**点赞 收藏 关注**我们下期再见