@Scheduled(cron = “0 */5 * * * ?“) 详解

@Scheduled(cron = "0 /5 ?") 执行原理详解

  1. Cron 表达式解析

"0 */5 * * * ?"

│ │ │ │ │ └─ 星期几(?表示不指定)

│ │ │ │ └─── 月份(*表示每月)

│ │ │ └───── 日期(*表示每日)

│ │ └─────── 小时(表示每小时)
│ └────────── 分钟(
/5表示每5分钟)

└───────────── 秒(0表示整秒触发)

触发时间示例:

• 14:00:00

• 14:05:00

• 14:10:00

• 14:15:00

• ...

  1. 执行原理时序图

Spring 应用启动

@EnableScheduling 扫描 @Scheduled 注解

注册 CronTask 到 TaskScheduler

TaskScheduler 内部线程定时检查 cron 表达式

匹配时间 → 触发任务执行

  1. 线程行为分析

默认配置(单线程)

// Spring 默认创建单线程调度器

ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

scheduler.setPoolSize(1); // 默认只有1个线程!

执行情况:

时间线:

14:00:00 - 线程A开始执行task3()

14:02:00 - task3()还在执行

14:05:00 - cron时间到,但线程A被占用,task3()排队等待

14:07:00 - 线程A完成任务 → 立即执行排队的task3()

问题:任务会排队堆积,实际执行间隔不是严格的5分钟!

自定义线程池配置

@Configuration

@EnableScheduling

public class SchedulerConfig {

@Bean

public TaskScheduler taskScheduler() {

ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

scheduler.setPoolSize(5); // 多线程

return scheduler;

}

}

执行情况:

时间线:

14:00:00 - 线程A执行task3()

14:05:00 - 线程B执行task3()(并行执行)

14:10:00 - 线程C执行task3()(并行执行)

  1. 与其他调度方法的区别

调度方式 触发时机 线程行为 适用场景

fixedDelay 上次任务结束后固定时间 严格不重叠 数据同步

fixedRate 固定频率,不等待完成 可能重叠 监控任务

cron 绝对时间点 可能排队/重叠 定时报表

  1. 实际代码演示

@Component

@Slf4j

public class CronTaskDemo {

复制代码
private final AtomicInteger executionCount = new AtomicInteger(0);

@Scheduled(cron = "0 */5 * * * ?")  // 每5分钟整点执行
public void task3() {
    int count = executionCount.incrementAndGet();
    String threadName = Thread.currentThread().getName();
    long startTime = System.currentTimeMillis();
    
    log.info("=== 第{}次执行开始 [线程:{}] ===", count, threadName);
    
    // 模拟任务执行(假设需要2分钟)
    try {
        Thread.sleep(120000);  // 2分钟
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    
    long cost = System.currentTimeMillis() - startTime;
    log.info("=== 第{}次执行结束 [耗时:{}ms] ===", count, cost);
}

}

运行结果(默认单线程):

14:00:00 - === 第1次执行开始 线程:pool-1-thread-1 ===

14:02:00 - === 第1次执行结束 耗时:120000ms ===

14:05:00 - === 第2次执行开始 线程:pool-1-thread-1 === ← 准时触发

14:07:00 - === 第2次执行结束 耗时:120000ms ===

运行结果(任务耗时超过5分钟的情况):

14:00:00 - === 第1次执行开始 线程:pool-1-thread-1 ===

14:05:00 - cron时间到,但线程被占用,任务排队

14:07:00 - === 第1次执行结束 耗时:420000ms ===

14:07:00 - === 第2次执行开始 线程:pool-1-thread-1 === ← 立即执行排队任务

  1. 数据同步场景的特殊考虑

风险:任务执行时间 > 5分钟

@Scheduled(cron = "0 */5 * * * ?")

public void dataSync() {

// 总耗时可能超过5分钟!

}

解决方案

@Component

@Slf4j

public class SafeCronDataSync {

复制代码
private final AtomicBoolean isRunning = new AtomicBoolean(false);

@Scheduled(cron = "0 */5 * * * ?")
public void dataSync() {
    // 防止任务重叠
    if (!isRunning.compareAndSet(false, true)) {
        log.warn("前一个数据同步任务仍在执行,跳过本次cron触发");
        return;
    }
    
    try {
        // 实际数据同步逻辑
        doDataSync();
    } finally {
        isRunning.set(false);
    }
}

private void doDataSync() {
    long start = System.currentTimeMillis();
    
    // 1. 查询数据库
    List<Map<String, Object>> data = queryData();
    
    // 2. 异步发送Kafka(不阻塞cron线程)
    CompletableFuture.runAsync(() -> {
        sendToKafka(data);
    });
    
    long cost = System.currentTimeMillis() - start;
    log.info("数据同步任务完成,耗时: {}ms", cost);
}

}

  1. 最佳实践总结

  2. cron适合按绝对时间点执行,如整点报表

  3. 数据同步推荐使用fixedDelay,避免时间偏差累积

  4. 必须配置多线程池,避免任务排队

  5. 必须添加防重叠机制,防止数据重复处理

  6. Kafka发送要用异步,不阻塞调度线程

对于你的场景,推荐:

@Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 10000) // 更合适

public void dataSync() {

// 防重叠 + 异步发送

}

@Scheduled(cron = "0 */5 * * * ?") 在任务执行时间可控(<3分钟)且需要严格按时间点执行时可以使用,但需要做好防重叠和线程池配置。

相关推荐
wuminyu14 小时前
虚拟线程底层ForkJoinPool的工作窃取算法机制
java·linux·c语言·jvm·c++
掉鱼的猫14 小时前
Solon 的日志设计哲学:不只是用 SLF4J 做统一门面
java
szephyr14 小时前
腾讯云 ADP 智能体的 Skills 配置变更后没有走灰度,直接全量替换出了问题怎么定位?
java·前端·腾讯云·腾讯云adp
evans在进步14 小时前
LeetCode 200:岛屿数量——Java DFS 染色法详解
java·leetcode·深度优先
带刺的坐椅15 小时前
Solon 的日志设计哲学:不只是用 SLF4J 做统一门面
java·logback·solon·log4j2
Flittly15 小时前
【雕虫大技】Agent 动态 Skill 供应链安全加固(二):执行隔离沙箱实战
java·spring boot·spring
程序员黑豆15 小时前
Java变量详解:从入门到精通
java·前端·ai编程
karry_k15 小时前
Skill 到底是什么?从提示词、脚本到多智能体工作流的完整指南
java·人工智能·后端
qq_4017004115 小时前
Qt自定义信号槽详解:带参发射 vs sender()获取,两种多信号关联方案全掌握
java·数据库·qt
周杰伦fans16 小时前
数列极限的定义
java·开发语言