杰瑞科技汇

Java Timer定时任务如何精确执行与取消?

  1. java.util.Timerjava.util.TimerTask (JDK 内置,简单易用)
  2. ScheduledExecutorService (JDK 内置,功能更强大,推荐)
  3. 第三方框架 Spring / Spring Boot (企业级应用首选)

java.util.Timerjava.util.TimerTask

这是 Java 早期提供的定时任务方案,非常简单,适合执行一些独立的、不需要复杂管理的后台任务。

Java Timer定时任务如何精确执行与取消?-图1
(图片来源网络,侵删)

核心概念

  • Timer: 一个调度器,负责管理 TimerTask 的执行,它是一个后台线程。
  • TimerTask: 一个抽象类,继承它并重写 run() 方法,就可以定义你想要执行的任务。

基本用法

步骤:

  1. 创建一个 TimerTask 的子类,并实现 run() 方法。
  2. 创建一个 Timer 实例。
  3. 调用 Timerschedule()scheduleAtFixedRate() 方法来安排任务。

示例代码:

import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
    public static void main(String[] args) {
        // 1. 创建一个TimerTask任务
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时任务执行了!当前时间: " + new java.util.Date());
            }
        };
        // 2. 创建一个Timer调度器
        Timer timer = new Timer();
        // 3. 安排任务执行
        // 方式一:延迟1秒后执行,之后每隔2秒执行一次
        // timer.schedule(task, 1000, 2000);
        // 方式二:指定在未来的某个具体时间点执行,之后每隔2秒执行一次
        // timer.scheduleAtFixedRate(task, new java.util.Date(System.currentTimeMillis() + 1000), 2000);
        // 方式三:只延迟1秒后执行一次
        timer.schedule(task, 1000);
        // 为了让程序能持续运行,观察定时任务的效果
        try {
            Thread.sleep(10000); // 主线程睡眠10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 4. 取消定时器(非常重要!)
        timer.cancel();
        System.out.println("定时器已取消。");
    }
}

schedule vs scheduleAtFixedRate

  • schedule(TimerTask task, long delay, long period)

    • 特点:固定延迟执行。
    • 逻辑:任务执行完成后,会等待 period 毫秒,然后再执行下一次任务。
    • 适用场景:不要求任务执行时间间隔绝对精确,允许因任务执行时间过长而导致间隔变长。
  • scheduleAtFixedRate(TimerTask task, long delay, long period)

    Java Timer定时任务如何精确执行与取消?-图2
    (图片来源网络,侵删)
    • 特点:固定频率执行。
    • 逻辑:它会尽量保证任务以 period 毫秒为周期来执行,如果某个任务执行时间超过了 period,它会“追赶”进度,尽快执行下一次任务,导致两次任务之间的间隔会小于 period
    • 适用场景:要求任务执行频率尽可能稳定,例如每秒心跳一次。

Timer 的缺点

  1. 基于单线程:所有任务都在同一个线程中串行执行,如果一个任务执行时间过长,会阻塞其他任务的执行。
  2. 异常处理脆弱TimerTask 中的 run() 方法抛出了未捕获的异常,会导致整个 Timer 线程终止,之后所有定时任务都不会再执行。
  3. 功能有限:无法灵活地管理任务(如暂停、恢复),也无法处理更复杂的调度逻辑(如“每天凌晨2点执行”)。

ScheduledExecutorService

从 Java 5 开始,java.util.concurrent 包提供了 ScheduledExecutorService,它是 ExecutorService 的扩展,功能更强大,是目前 Java 原生定时任务的首选方案。

核心概念

  • ScheduledExecutorService: 一个线程池,专门用于调度和执行延迟任务或周期性任务。
  • ScheduledFuture: 代表一个已经调度的任务,可以用来获取任务的结果、取消任务或检查任务是否完成。

基本用法

步骤:

  1. 通过 Executors 工厂类创建一个 ScheduledExecutorService 实例。
  2. 使用 schedule(), scheduleAtFixedRate(), scheduleWithFixedDelay() 方法提交任务。
  3. 当不再需要时,调用 shutdown() 关闭线程池。

示例代码:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) {
        // 1. 创建一个线程池,核心线程数为1
        ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
        // 2. 定义一个任务(使用Runnable或Callable)
        Runnable task = () -> {
            System.out.println("线程池定时任务执行了!当前时间: " + new java.util.Date());
            // 模拟一个耗时较长的任务
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        // 3. 安排任务执行
        // 方式一:延迟1秒后执行,之后每隔2秒执行一次(固定延迟)
        // scheduledExecutor.scheduleWithFixedDelay(task, 1, 2, TimeUnit.SECONDS);
        // 方式二:延迟1秒后执行,之后每隔2秒执行一次(固定频率)
        // 如果任务执行时间超过2秒,下一次任务会“追赶”
        scheduledExecutor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
        // 方式三:只延迟1秒后执行一次
        // scheduledExecutor.schedule(task, 1, TimeUnit.SECONDS);
        // 为了观察效果
        try {
            Thread.sleep(20000); // 主线程睡眠20秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 4. 关闭线程池(非常重要!)
        scheduledExecutor.shutdown();
        System.out.println("线程池已关闭。");
    }
}

ScheduledExecutorService 的优点

  1. 基于线程池:可以管理多个线程,避免了 Timer 的单线程阻塞问题,一个任务失败不会影响其他任务。
  2. 异常处理健壮:任务抛出的异常不会导致整个线程池终止,只会影响当前任务。
  3. 功能更强大:提供了更丰富的 API,可以方便地取消任务(通过 ScheduledFuture),并且支持 Callable 任务,可以获取返回值。
  4. 更灵活的关闭机制:提供了 shutdown()shutdownNow() 方法,可以优雅地关闭线程池。

Spring / Spring Boot 的定时任务 (@Scheduled)

在企业级应用开发中,我们通常使用 Spring 框架来管理定时任务,Spring 提供了基于注解的 @Scheduled,使用起来非常简洁和方便。

Java Timer定时任务如何精确执行与取消?-图3
(图片来源网络,侵删)

核心概念

  • @EnableScheduling: 在 Spring 配置类(或带有 @Configuration 的类)上添加此注解,以启用对 @Scheduled 注解的支持。
  • @Scheduled: 在方法上添加此注解,该方法将成为一个定时任务。

基本用法

步骤:

  1. 在 Spring Boot 的主启动类或配置类上添加 @EnableScheduling
  2. 在需要执行定时任务的方法上添加 @Scheduled 注解,并配置其执行规则。

示例代码:

主启动类 / 配置类

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);
    }
}

定时任务服务类

import org.springframework.stereotype.Component;
import java.util.Date;
@Component // 必须被 Spring 容器管理
public class ScheduledTasks {
    // cron 表达式,每5秒执行一次
    @Scheduled(cron = "0/5 * * * * ?")
    public void taskByCron() {
        System.out.println("Cron定时任务执行了!当前时间: " + new Date());
    }
    // 固定延迟执行:上一次任务结束后,等待3秒再执行下一次
    @Scheduled(fixedDelay = 3000)
    public void taskWithFixedDelay() {
        System.out.println("FixedDelay定时任务执行了!当前时间: " + new Date());
        try {
            Thread.sleep(1000); // 模拟耗时1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 固定频率执行:每隔3秒执行一次(不受任务执行时间影响)
    @Scheduled(fixedRate = 3000)
    public void taskWithFixedRate() {
        System.out.println("FixedRate定时任务执行了!当前时间: " + new Date());
    }
}

@Scheduled 的主要属性

  • cron: 最强大的属性,使用 Cron 表达式来定义复杂的调度规则。
    • 格式: 秒 分 时 日 月 周
    • 示例:
      • "0 0 2 * * ?": 每天凌晨2点执行。
      • "0 0/5 14,18 * * ?": 每天14点和18点,每隔5分钟执行一次。
      • "0 15 10 ? * MON-FRI": 每周一至周五的10:15执行。
  • fixedDelay: 固定延迟,单位是毫秒,表示上一次任务执行完成后,延迟多长时间再执行下一次。
  • fixedRate: 固定频率,单位是毫秒,表示每隔多长时间执行一次任务,与任务执行时间无关。
  • initialDelay: 初始延迟,单位是毫秒,表示在第一次任务执行前,等待多长时间,通常与 fixedDelayfixedRate 一起使用。
    • @Scheduled(fixedRate = 3000, initialDelay = 1000) 表示程序启动后1秒执行第一次,之后每隔3秒执行一次。

总结与对比

特性 java.util.Timer ScheduledExecutorService Spring @Scheduled
来源 JDK 1.3 JDK 1.5 Spring Framework
线程模型 单线程 线程池 Spring Bean (默认单例,方法在Spring管理的线程中执行)
异常处理 差,会终止整个调度器 好,异常只影响当前任务 好,异常由 Spring 框架统一处理
功能 基础,简单 强大,灵活 非常强大,支持Cron表达式,与Spring生态集成
适用场景 简单的、独立的任务 Java原生应用中需要灵活调度的任务 Spring / Spring Boot 应用
管理 手动 cancel() 手动 shutdown() 由 Spring 容器生命周期管理
推荐度 ⭐⭐ (已过时,不推荐新项目使用) ⭐⭐⭐⭐ (Java原生方案首选) ⭐⭐⭐⭐⭐ (企业级应用首选)

如何选择?

  • 如果你在写一个简单的、独立的 Java 程序,没有引入任何框架,并且任务非常简单,可以考虑使用 ScheduledExecutorService
  • 如果你正在使用 Spring 或 Spring Boot 框架开发,请毫不犹豫地选择 @Scheduled,它与 Spring 的 IoC、AOP 等特性无缝集成,使用体验最好,功能也最完善。
  • 尽量避免在新项目中使用 java.util.Timer,除非你维护的是一个古老的遗留系统,它的单线程和脆弱的异常处理机制是它致命的缺点。
分享:
扫描分享到社交APP
上一篇
下一篇