Java 定时任务完全指南

> 彻底搞懂 Java 中的定时任务,看这一篇就够了

前言

什么是定时任务?简单说就是**让程序在指定时间自动执行某个任务**。比如:

  • 每天凌晨 3 点跑批处理

  • 每隔 5 分钟检查订单状态

  • 每周一早上 9 点发送周报

  • 每月 1 号生成财务报表

这些场景,都离不开定时任务。

本文会详细介绍 Java 中实现定时任务的 **4 种主流方式**,并配上完整的代码示例,保证你看完就能上手用。


一、为什么需要定时任务?

在正式讲代码之前,先说说定时任务能做什么,了解清楚它的应用场景。

定时任务的典型应用

| 场景 | 说明 |

|------|------|

| **数据同步** | 定时从第三方 API 拉取数据到本地 |

| **报表生成** | 每天/每周/每月自动生成统计报表 |

| **缓存清理** | 定时清理过期缓存,释放内存 |

| **订单处理** | 定时检查超时未支付订单,自动取消 |

| **通知推送** | 定时推送消息、邮件、短信 |

| **数据备份** | 凌晨定时备份数据库 |

| **健康检查** | 定时检查服务是否存活 |

看到了吧,定时任务的用途非常广泛,几乎每个业务系统都离不开它。

二、Java 定时任务的 4 种实现方式

接下来进入正题,看看 Java 中怎么实现定时任务。

Timer + TimerTask(最简单)

这是 Java 自带的最原始的方式,代码简单,适合简单场景。

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();
    }
}
java 复制代码
**运行结果:**

任务执行了!当前时间:1647834000000
第 1 次执行,时间:1647834001000
第 2 次执行,时间:1647834003000
第 3 次执行,时间:1647834005000

schedule() vs scheduleAtFixedRate() 的区别:

  • `schedule()`:下一次任务开始时间 = 上一次任务结束时间 + 间隔

  • `scheduleAtFixedRate()`:下一次任务开始时间 = 上一次任务开始时间 + 间隔(固定频率)

简单说,如果任务执行时间超过了间隔,schedule 会"追不上",scheduleAtFixedRate 会"追赶"。


ScheduledExecutorService(推荐使用)

这是"JDK 1.5+"提供的更强大的方式,**线程池**实现,更稳定、更高效。

java 复制代码
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():

java 复制代码
// scheduleAtFixedRate:固定频率
// 上次开始时间 + 间隔 = 下次开始时间
// 适合:任务执行时间 < 间隔时间的场景

// scheduleWithFixedDelay:固定延迟
// 上次结束时间 + 间隔 = 下次开始时间
// 适合:任务执行时间不确定,或者任务之间需要隔离的场景

Spring @Scheduled(Spring 用户的首选)

如果你使用 Spring Framework,这个注解就是你的首选。配置简单,注解驱动。

java 复制代码
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 启动类配置:

java 复制代码
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 完整参数说明:

java 复制代码
@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 是更好的选择。

java 复制代码
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 中的使用:

java 复制代码
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:每天凌晨同步数据

java 复制代码
@Scheduled(cron = "0 0 2 * * ?")
public void syncData() {
    log.info("开始同步数据...");
    try {
        // 1. 从第三方 API 获取数据
        List<Data> dataList = externalApi.fetchData();
        
        // 2. 处理数据
        List<Data> processed = processor.process(dataList);
        
        // 3. 保存到数据库
        repository.saveAll(processed);
        
        log.info("数据同步完成,共处理 {} 条数据", processed.size());
    } catch (Exception e) {
        log.error("数据同步失败", e);
        // 可以发送告警通知
        notifyService.alert("数据同步失败:" + e.getMessage());
    }
}

场景 2:定期清理过期订单

java 复制代码
@Scheduled(fixedDelay = 300000)  // 每 5 分钟执行一次
public void cleanExpiredOrders() {
    log.info("开始清理过期订单...");
    
    // 查询超过 30 分钟未支付的订单
    List<Order> 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:心跳检测

java 复制代码
@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. 时区问题

java 复制代码
// 默认使用服务器时区,注意 Cron 表达式的时区
@Scheduled(cron = "0 0 9 * * ?")  // 服务器时区早上9点

// 指定时区
@Scheduled(cron = "0 0 9 * * ?", zone = "Asia/Shanghai")  // 上海时区早上9点

2. 任务执行时间超过间隔

java 复制代码
// 如果任务执行时间是 8 秒,间隔是 5 秒
// schedule:任务完成后等 5 秒再执行下一次
// scheduleAtFixedRate:立即执行下一次(会"追赶")

// 建议:任务执行时间 > 间隔时间时,使用 scheduleWithFixedDelay
@Scheduled(fixedDelay = 5000)  // 上次结束后等 5 秒

3. 集群环境下的定时任务

在多实例部署时,如果不处理,多个实例会同时执行同一个定时任务,导致数据问题。

解决方案:

  1. **Quartz 集群**:自带分布式锁

  2. **Spring @Scheduled + Redis 分布式锁**

  3. **数据库行锁**

  4. **分布式调度中间件**:XXL-Job、ElasticJob

4. 优雅关闭

java 复制代码
@PreDestroy
public void shutdown() {
    log.info("关闭定时任务调度器...");
    if (scheduler != null) {
        scheduler.shutdown();
    }
}

六、总结

  1. **简单场景**:用 `Timer` 或 `ScheduledExecutorService`

  2. **Spring 项目**:用 `@Scheduled` 注解,最方便

  3. **复杂企业级**:用 `Quartz`,功能最强大

  4. **集群环境**:选 `Quartz` 或分布式调度框架

定时任务是后端开发的基本功,面试也经常问。

建议大家**自己动手写一遍**,跑跑代码,感受一下不同方式的区别。纸上得来终觉浅,绝知此事要躬行。

如果觉得有帮助,**点赞 + 收藏 + 关注**,我们下期再见!

相关推荐
毕设源码-郭学长2 小时前
【开题答辩全过程】以 高校自动排课系统的设计与实现为例,包含答辩的问题和答案
java
indexsunny2 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的深度解析
java·spring boot·spring cloud·kafka·prometheus·security·microservices
ChoSeitaku2 小时前
NO.2|proto3语法|消息类型|通讯录|文件读取|enum类型
java·服务器·前端
庞轩px3 小时前
MinorGC的完整流程与复制算法深度解析
java·jvm·算法·性能优化
zhouping@3 小时前
JAVA学习笔记day06
java·笔记·学习
毕设源码-郭学长3 小时前
【开题答辩全过程】以 某某协会管理与展示平台为例,包含答辩的问题和答案
java
多云的夏天3 小时前
docker容器部署-windows-ubuntu
java·docker·容器
庞轩px3 小时前
内存区域的演进与直接内存——JVM性能优化的权衡艺术
java·jvm·笔记·性能优化
编码忘我3 小时前
java多线程安全集合
java