Spring 定时任务

268 阅读3分钟

简介

Spring提供了任务调度的@Scheduled注解

入门

  1. 安装依赖

    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
  2. 编写任务类

    @Component
    public class ScheduleTask {
    
        private final Logger log = LoggerFactory.getLogger(ScheduleTask.class);
    
        @Scheduled(cron = "1/2 * * * * ?")
        public void task1() throws InterruptedException {
            log.info("task-1, 我需要执行10秒的时间, 线程Id:{}, 线程名:{},时间:{}",
                    Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
            Thread.sleep(10000);
            log.error("task-1 ending....., 线程Id:{}, 线程名:{},时间:{}",
                    Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
        }
    }
    
  3. 配置主启动类

    @SpringBootApplication
    // 开启定时任务功能
    @EnableScheduling
    public class SchedulerApplication {
        public static void main(String[] args) {
            SpringApplication.run(SchedulerApplication.class, args);
        }
    }
    
  4. 启动运行

@Scheduled

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
	String CRON_DISABLED = "-";

	String cron() default "";

	String zone() default "";
    
	long fixedDelay() default -1;

	String fixedDelayString() default "";

	long fixedRate() default -1;

	String fixedRateString() default "";

	long initialDelay() default -1;

	String initialDelayString() default "";
}

cron :corn表达式可以灵活的配置任务的执行时间

@Scheduled(cron = "0 * * * * MON-FRI")
public void scheduleTaskUsingCronExpression() {
 // 省略
}

fixedDelay :配置一个固定延迟后运行的任务。 控制最后一次执行完成时的下一个执行时间 ,上一个任务执行完成后,过多长时间执行下一个任务

@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
    // 省略
}

fixedRate :使Spring定期运行任务,即配置固定的时间间隔执行任务。如果上一个任务阻塞,下一个任务也不会执行,默认使用的同一个线程,可以开启异步注解*@Async*,但是可能会产生数据不一致问题

@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
    // 省略
}

zone :时区 initialDelay :容器启动后,延迟多久执行任务

多任务并发执行

现在再添加一个定时任务,测试当跑多个定时任务

@Scheduled(cron = "1/4 * * * * ?")
public void task2() throws InterruptedException {
    log.info("task-2, 我需要执行两秒时间, 线程Id:{}, 线程名:{},时间:{}",
             Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
    Thread.sleep(2000);
    log.error("task-2 ending....., 线程Id:{}, 线程名:{},时间:{}",
              Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
}

执行结果

可以发现两个任务是串行执行的,其他任务必须等到另一任务执行完成才能执行 默认情况下,Spring Boot将仅使用一个线程来运行所有计划的任务

创建了一个新的配置类,该类扩展了SchedulingConfigurer。能够配置任务计划程序,并传入我们要使用的线程池大小

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {

    private int corePoolSize = 10;
    private String threadNamePrefix = "scheduled-task-pool-";

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(corePoolSize);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.initialize();
        taskRegistrar.setScheduler(executor);
    }
}

配置完成,执行可以发现两个定时任务可以并发执行

动态配置执行时间

之前的执行时间都是写死在代码中的,现在通过数据库动态配置执行时间。 数据库脚本

drop table if exists `spring_scheduled_cron`;
create table `spring_scheduled_cron` (
  `cron_id`         int primary key           auto_increment
  comment '主键id',
  `cron_key`        varchar(128) not null unique
  comment '定时任务完整类名',
  `cron_expression` varchar(20)  not null
  comment 'cron表达式',
  `task_explain`    varchar(50)  not null     default ''
  comment '任务描述',
  `status`          tinyint      not null     default 1
  comment '状态,1:正常;2:停用',
  unique index cron_key_unique_idx(`cron_key`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COMMENT = '定时任务表';

insert into `spring_scheduled_cron`
values (1, 'com.kxj.task.DynamicPrintTask', '*/5 * * * * ?', '定时任务描述', 1);
insert into `spring_scheduled_cron`
values (2, 'com.kxj.task.DynamicPrintTask1', '*/5 * * * * ?', '定时任务描述1', 1);
insert into `spring_scheduled_cron`
values (3, 'com.kxj.task.DynamicPrintTask2', '*/5 * * * * ?', '定时任务描述2', 1);

实体类

@Data
@ToString
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "spring_scheduled_cron")
public class SpringScheduledCron {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cron_id")
    private Integer cronId;

    @Column(name = "cron_key", unique = true)
    private String cronKey;

    @Column(name = "cron_expression")
    private String cronExpression;

    @Column(name = "task_explain")
    private String taskExplain;

    @Column(name = "status")
    private Integer status;

}

Scheduled配置类

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
  
    private int corePoolSize = 10;
    @Autowired
    SpringScheduledCronRepository springScheduledCronRepository;
    @Autowired
    ApplicationContext context;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler executor = getThreadPoolTaskScheduler();
        // 设置线程池
        taskRegistrar.setScheduler(executor);

        List<SpringScheduledCron> springScheduledCrons = springScheduledCronRepository.findAll();
        for (SpringScheduledCron springScheduledCron : springScheduledCrons) {
            Class<?> clazz = null;
            Object task = null;
            try {
                // 利用反射读取数据库配置的定时任务
                clazz = Class.forName(springScheduledCron.getCronKey());
                task = context.getBean(clazz);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            taskRegistrar.addTriggerTask(((Runnable) task), triggerContext -> {
                String cronExpression = springScheduledCronRepository.findByCronKey(springScheduledCron.getCronKey()).getCronExpression();
                return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
            });
        }
    }

    private ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(corePoolSize);
        executor.setThreadNamePrefix("scheduled-task-pool-");
        executor.initialize();
        return executor;
    }
}

定时任务

// 也可以直接实现Runnable接口,重写run方法
@Component
public class DynamicPrintTask implements ScheduledOfTask {
    private Logger logger = LoggerFactory.getLogger(getClass());

    private int i;

    @Override
    public void execute() {
        logger.info("thread id:{},DynamicPrintTask execute times:{}", Thread.currentThread().getId(), ++i);
    }
}
// ScheduledOfTask1和ScheduledOfTask2同ScheduledOfTask
public interface ScheduledOfTask extends Runnable {
    /**
     * 定时任务方法
     */
    void execute();
    /**
     * 实现控制定时任务启用或禁用的功能
     */
    @Override
    default void run() {
        SpringScheduledCronRepository repository = SpringUtils.getBean(SpringScheduledCronRepository.class);
        SpringScheduledCron scheduledCron = repository.findByCronKey(this.getClass().getName());
        if (StatusEnum.DISABLED.getCode().equals(scheduledCron.getStatus())) {
            // 任务是禁用状态
            return;
        }
        execute();
    }
}

public enum  StatusEnum {
    ENABLED(1),
    DISABLED(2) ;

    private Integer code;

    StatusEnum(int code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

待写-结合Calender使用

待写-集群下的定时任务