@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分钟)且需要严格按时间点执行时可以使用,但需要做好防重叠和线程池配置。

相关推荐
没有bug.的程序员17 小时前
Serverless 弹性扩容引发的全线熔断:Spring Boot 启动耗时从 1s 压缩至 0.3s 的物理级绞杀
java·spring boot·kubernetes·serverless·扩容·线上
bearpping17 小时前
java进阶知识点
java·开发语言
独自破碎E17 小时前
【面试真题拆解】你知道ThreadLocal是什么吗
java·jvm·面试
kkkkatoq17 小时前
JAVA中的IO操作
java·开发语言
深蓝轨迹17 小时前
@Autowired与@Resource:Spring依赖注入注解核心差异剖析
java·python·spring·注解
不想看见40417 小时前
C++八股文【详细总结】
java·开发语言·c++
huaweichenai18 小时前
java的数据类型介绍
java·开发语言
weisian15118 小时前
Java并发编程--17-阻塞队列BlockingQueue:生产者-消费者模式的最佳实践
java·阻塞队列·blockqueue
奔跑的呱呱牛18 小时前
GeoJSON 在大数据场景下为什么不够用?替代方案分析
java·大数据·servlet·gis·geojson
爱丽_18 小时前
Pinia 状态管理:模块化、持久化与“权限联动”落地
java·前端·spring