所有分类
  • 所有分类
  • 未分类

Spring定时任务-@Scheduled注解-使用/教程/实例

简介

说明

本文用示例介绍SpringBoot的@Scheduled注解的用法。

执行时间的配置

在方法上使用@Scheduled注解来设置任务的执行时间,并且使用三种属性配置方式:

fixedRatefixedDelaycron
描述每隔多少时间就启动任务,不管该任务是否启动完成。单位:默认为毫秒每次执行任务完成之后间隔多久再次执行该任务。单位:默认为毫秒根据cron表达式执行
执行时机项目启动时即开始项目启动时即开始根据cron表达式执行

initialDelay:指定第一次执行的延时时间。

如:@Scheduled(initialDelay = 1000, fixedRate = 3000) :第一次在延迟1秒后执行,之后按fixedRate的规则每 3 秒执行一次。

配置文件中指定cron

假如application.yml中指定:custom.schedule.cron.task1: 0/5 * * * * *

代码中可以这么写:@Scheduled(cron = “${custom.schedule.cron.task1}”)

配置文件中指定fixedDelay

假如application.yml中指定:custom.schedule.fixed-delay.task11: 1000

代码中可以这么写:@Scheduled(fixedDelayString = “${custom.schedule.fixed-delay.task1}”)

基础实例

前提条件

添加SpringBoot依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

启用定时器(主类添加@EnableScheduling)

@SpringBootApplication
@EnableScheduling
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

示例1

@Component
public class MyTest {
    @Scheduled(cron = "0/5 * * * * *")
    public void scheduled() {
        System.out.println("this is cron");
    }
}

执行结果

this is cron: 1589472135001

示例2

@Component
public class MyTest {
    @Scheduled(cron = "0/5 * * * * *")
    public void scheduled1() {
        System.out.println("this is cron: " + System.currentTimeMillis());
    }

    @Scheduled(fixedRate = 5000)
    public void scheduled2(){
        System.out.println("this is fixedRate: " + System.currentTimeMillis());
    }

    @Scheduled(fixedDelay = 5000)
    public void scheduled3(){
        System.out.println("this is fixedDelay: " + System.currentTimeMillis());
    }
}

执行结果(它们是串行执行的)

this is cron: 1589472145001
this is fixedRate: 1589472146341
this is fixedDelay: 1589472146342
this is cron: 1589472150002
this is fixedRate: 1589472151342
this is fixedDelay: 1589472151343
this is cron: 1589472155001
this is fixedRate: 1589472156342
this is fixedDelay: 1589472156343

单线程问题

问题描述

简介

@Scheduled默认是单线程执行的,如果前一个任务执行时间过长,则后一个任务会被延迟,示例如下:

启动类

package com.knife.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

测试类

package com.knife.example.schedule;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class DemoSchedule {
    @Scheduled(cron = "0/1 * * * * ?")
    public void task1() {
        log.info("task1");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void task2() {
        log.info("task2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:(前一个任务执行完,才会执行下一个任务)

2024-07-07 14:58:39.013  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 14:58:44.020  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 14:58:49.035  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 14:58:54.038  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 14:58:59.050  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 14:59:04.054  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 14:59:09.067  INFO 19280 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1

解决方案1:配置线程数量

配置定时任务线程池的线程个数

修改application.yml 

spring:
  task:
    scheduling:
      pool:
        size: 3
      shutdown:
        # 定时线程池关闭时等待所有任务完成
        await-termination: true

或者:

package com.knife.example.schedule;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
    }
}

结果:并行执行,每个任务都会等上一个任务结束再执行。

2024-07-07 15:03:33.007  INFO 11452 --- [   scheduling-2] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 15:03:33.007  INFO 11452 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:03:39.003  INFO 11452 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 15:03:39.003  INFO 11452 --- [   scheduling-2] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:03:45.014  INFO 11452 --- [   scheduling-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:03:45.014  INFO 11452 --- [   scheduling-3] com.knife.example.schedule.DemoSchedule  : task2

解决方案2:自定义定时任务的线程池 

@Scheduled的默认线程池是:ThreadPoolTaskScheduler这个Bean,最终调用到ThreadPoolExecutor的runWorker(Worker w)。

如果自定义了TaskScheduler,那么就会使用你自定义的,不会使用默认的这个ThreadPoolTaskScheduler。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Configuration
public class ScheduledConfig {
    private Integer poolSize = 10;

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadFactory(new CustomizableThreadFactory("my-scheduled-task-"));

        // 设置线程池大小
        scheduler.setPoolSize(poolSize);
        // 线程名称前缀
        scheduler.setThreadNamePrefix("scheduled-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 自定义RejectedExecutionHandler
        scheduler.setRejectedExecutionHandler(this::rejectHandler);
        return scheduler;
    }

    private void rejectHandler(Runnable r, ThreadPoolExecutor executor) {
        log.error("定时任务线程池饱和");
        // executor.getQueue().clear();
        // executor.purge();
    }
}

结果跟上边一样。

解决方案3:异步

用法:启动类加@EnableAsync,@Scheduled类或者方法上加@Async。注意:此法会使 fixedDelay策略失效。 ​

@Async会开启异步,使用新线程去执行任务。详见:Spring-@Async异步执行的方法 – 自学精灵

启动类:

package com.knife.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

测试类:

package com.knife.example.schedule;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Async
public class DemoSchedule {
    @Scheduled(cron = "0/1 * * * * ?")
    public void task1() {
        log.info("task1");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void task2() {
        log.info("task2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:并行执行,真正按照cron一秒一次!

2024-07-07 15:08:34.010  INFO 14068 --- [         task-2] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 15:08:34.010  INFO 14068 --- [         task-1] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:08:35.015  INFO 14068 --- [         task-3] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:08:35.015  INFO 14068 --- [         task-4] com.knife.example.schedule.DemoSchedule  : task2
2024-07-07 15:08:36.005  INFO 14068 --- [         task-5] com.knife.example.schedule.DemoSchedule  : task1
2024-07-07 15:08:36.005  INFO 14068 --- [         task-6] com.knife.example.schedule.DemoSchedule  : task2
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录