深入理解 Java `ScheduledExecutorService` 和 Spring `@Scheduled` 注解:重点讲解 Cron 表达式

487 阅读5分钟

定时任务在很多应用场景中都有着广泛的使用,尤其是在需要周期性执行任务时。Java 和 Spring 提供了强大的定时任务支持,今天我们将深入探讨两种常见的定时任务机制:ScheduledExecutorService 和 Spring 的 @Scheduled 注解,重点介绍如何使用 Cron 表达式来设置任务执行时间。

一、Java ScheduledExecutorService

ScheduledExecutorService 是 Java 5 引入的一个接口,它提供了灵活且高效的方式来执行定时任务,主要用于周期性和延时的任务调度。相比于传统的 TimerTimerTaskScheduledExecutorService 在线程安全性、性能和异常处理方面表现得更加优秀。

1.1 ScheduledExecutorService 简单使用

ScheduledExecutorService 接口有多个实现类,其中最常用的是 Executors.newScheduledThreadPool(int corePoolSize),它创建一个支持定时任务的线程池。我们可以使用 schedulescheduleAtFixedRate 等方法来调度任务。

示例代码:

import java.util.concurrent.*;

public class ScheduledExecutorServiceExample {

    public static void main(String[] args) {
        // 创建一个线程池,最多两个核心线程
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

        // 定义任务
        Runnable task = () -> System.out.println("任务执行时间:" + System.currentTimeMillis());

        // 延迟 1 秒执行任务,然后每 3 秒执行一次
        executorService.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
    }
}

1.2 解释 ScheduledExecutorService 的工作机制

  • scheduleAtFixedRate:任务首先延迟 1 秒执行,然后每隔 3 秒执行一次。
  • scheduleWithFixedDelay:任务延迟 1 秒执行,然后在上一次任务执行完成后的 3 秒再执行下一个任务(更常用于需要间隔执行的场景)。

ScheduledExecutorService 通过线程池来调度任务,因此你可以更好地控制并发任务和异常处理。此外,它还支持精确的定时任务调度和任务重复执行,适用于大多数生产环境。

二、Spring @Scheduled 注解

Spring 框架也提供了定时任务支持,通过 @Scheduled 注解,开发者可以轻松实现定时任务的管理。与 ScheduledExecutorService 不同,Spring @Scheduled 注解的配置更加简洁,适合大多数场景,特别是简单的周期性任务。

2.1 @Scheduled 注解的基本使用

使用 @Scheduled 注解,你只需要在方法上加上这个注解即可。Spring 会根据你提供的参数,自动定时执行该方法。

示例代码:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MyScheduledTask {

    // 每 5 秒执行一次
    @Scheduled(fixedRate = 5000)
    public void taskWithFixedRate() {
        System.out.println("固定频率任务执行:" + System.currentTimeMillis());
    }

    // 延迟 1 秒执行,之后每次任务完成后再延迟 2 秒执行
    @Scheduled(fixedDelay = 2000, initialDelay = 1000)
    public void taskWithFixedDelay() {
        System.out.println("固定延迟任务执行:" + System.currentTimeMillis());
    }

    // 每天午夜 12 点执行
    @Scheduled(cron = "0 0 0 * * ?")
    public void taskWithCron() {
        System.out.println("Cron 表达式定时任务:" + System.currentTimeMillis());
    }
}

2.2 @Scheduled 注解的三种常见使用方式

  1. fixedRate:任务以固定的时间间隔执行,无论上一个任务是否完成。例如,上面例子中的 taskWithFixedRate() 每 5 秒执行一次,不考虑任务执行时间。
  2. fixedDelay:每次任务完成后,等待指定的时间再执行下一个任务。taskWithFixedDelay() 每次任务执行完成后,延迟 2 秒再执行。
  3. cron:通过 Cron 表达式设置复杂的调度时间。Cron 表达式非常灵活,可以指定任务在特定的时间点执行。

三、重点讲解 Cron 表达式

3.1 什么是 Cron 表达式?

Cron 表达式是一种强大的定时任务调度方式,广泛用于 Unix 系统以及许多编程框架(如 Spring)中。Cron 表达式包含 6 或 7 个字段,允许你精确控制任务执行的时刻。

Cron 表达式的基本结构如下:

秒 (0 - 59) 
分钟 (0 - 59) 
小时 (0 - 23)
日期 (1 - 31)
月份 (1 - 12)
星期 (0 - 6) (星期日为 0)
年份 (可选字段)

每个字段可以包含一个数字、一个范围、一个逗号分隔的列表、一个步长表达式,或者特殊字符。常见的 Cron 表达式元素包括:

  • *:表示任何值,等价于“每个”。
  • ,:分隔符,用于指定多个值。
  • -:范围符号,表示一系列的值。
  • /:步长符号,表示步长(例如,*/5 表示每 5 分钟)。
  • ?:表示“不指定”,通常用于日期和星期的冲突时。
  • L:表示“最后”。
  • W:表示最近的工作日。

3.2 常见 Cron 表达式示例

1. 每天午夜执行:

0 0 0 * * ?
  • 秒:0(零秒)
  • 分钟:0(零分钟)
  • 小时:0(零点)
  • 日期:*(每天)
  • 月份:*(每月)
  • 星期:?(无特定要求)

2. 每隔 5 分钟执行一次:

0 */5 * * * ?
  • 秒:0
  • 分钟:*/5(每 5 分钟)
  • 小时:*(每小时)
  • 日期:*(每天)
  • 月份:*(每月)
  • 星期:?(无特定要求)

3. 每月的 1 号凌晨 3 点执行:

0 0 3 1 * ?
  • 秒:0
  • 分钟:0
  • 小时:3(凌晨 3 点)
  • 日期:1(每月的第 1 天)
  • 月份:*(每月)
  • 星期:?(无特定要求)

4. 每个星期一的中午 12 点执行:

0 0 12 ? * MON
  • 秒:0
  • 分钟:0
  • 小时:12(中午 12 点)
  • 日期:?(不指定日期)
  • 月份:*(每月)
  • 星期:MON(星期一)

3.3 使用 cron 在 Spring 中配置定时任务

在 Spring 中,我们可以使用 @Scheduled(cron = "cron表达式") 来定制更复杂的定时任务调度。例如:

@Scheduled(cron = "0 0 0 * * ?") // 每天午夜执行
public void midnightTask() {
    System.out.println("午夜定时任务:" + System.currentTimeMillis());
}

通过理解和掌握 Cron 表达式,我们可以灵活地设置任务执行的时间和频率。

四、总结

在 Java 和 Spring 中,ScheduledExecutorService@Scheduled 注解都是非常常见且强大的定时任务调度工具。ScheduledExecutorService 提供了更精细的线程控制和任务调度功能,适合复杂场景。Spring 的 @Scheduled 注解则提供了简洁易用的接口,适用于大多数简单的定时任务需求。

而 Cron 表达式则是定时任务调度中不可或缺的工具,它通过灵活的语法让开发者可以精确控制任务的执行时间。在 Spring 中,使用 Cron 表达式配置定时任务,不仅能够简化代码,还能提供更强的时间控制能力。掌握 Cron 表达式,将极大地提升你对定时任务调度的理解和应用。