springboot实现定时任务

Quartz

1. Quartz Scheduler

主要设计以下元素:

  • scheduler:任务调度器;
  • trigger:触发器,用于定义任务调度的时间规划;
  • job:被调度的任务;
  • misfire:错过的,指本来应该执行,但实际没有被执行的任务调度。

其中trigger和job是任务调度的元数据,scheduler是实际执行调度的控制器。

  • trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger: SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。
  • job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来 说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。
  • 一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。
  • scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 一般使用 StdSchedulerFactory 工厂 较多。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。最常用的为 StdScheduler。

2. Quartz 的线程

  • 在 Quartz 中,有两类线程,Scheduler 调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。
  • Scheduler 调度线程主要有两个: 执行常规调度的线程,和执行 misfired trigger 的线程。常规调度线程轮询存储的所有 trigger, 如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任 务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。

3. 数据存储

  • Quartz 中的 trigger 和 job 需要存储下来才能被使用。Quartz 中有两种存储方式:RAMJobStore, JobStoreSupport,其中RAMJobStore 是将 trigger 和 job 存储在内存中, 而 JobStoreSupport 是基于 jdbc 将 trigger 和 job 存储到数据库中。RAMJobStore 的存取速度非常快, 但是由于其在系统被停止后所有的数据都会丢失, 所以在通常应用中, 都是使用JobStoreSupport。
  • 在 Quartz 中,JobStoreSupport 使用一个驱动代理来操作 trigger 和 job 的数据存储:StdJDBCDelegate。StdJDBCDelegate 实现了大部分基于标准 JDBC 的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展 StdJDBCDelegate 以实现这些特殊处理。Quartz 已经自带了一些数据库的扩展实现,可以直接使用。

4. 使用方法

4.1 引入依赖

  • 这里springboot使用的是2.3.1.RELEASE
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

在这里插入图片描述

4.2 创建目标任务job

  1. 使用实现 org.quartz.Job 接口的方式创建任务BusinessJob

在这里插入图片描述

package com.example.demo.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@Slf4j
public class BusinessJob implements Job {
    int i = 0;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        String time = dataMap.get("time").toString();
        business(time);
    }

    public void business(String time) {
        i++;
        log.info("time:{},threadName:{},i:{}", time, Thread.currentThread().getName(), i);
    }
}
  1. 普通任务类

在这里插入图片描述

package com.example.demo.job;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class JobService {

    int i = 0;
    public void business() {
        i++;
        log.info("定时任务,业务方法执行,thread:{},i:{}", Thread.currentThread().getName(), i);
    }
}

4.3 创建配置类QuartzConfig

在这里插入图片描述

package com.example.demo.config;

import com.example.demo.job.BusinessJob;
import com.example.demo.job.JobService;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.*;

import java.time.LocalDateTime;

@Configuration
public class QuartzConfig {

    //配置任务,多例的业务bean,耦合业务类,需要实现Job接口
    @Bean(name = "businessJobDetail")
    public JobDetailFactoryBean businessJobDetail() {
        LocalDateTime localDateTime = LocalDateTime.now();
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(BusinessJob.class);
        //将参数传给job
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("time", localDateTime);
        jobDetailFactoryBean.setJobDataAsMap(jobDataMap);
        return jobDetailFactoryBean;
    }

    //配置任务,单例的业务bean
    @Bean(name = "jobServiceBeanDetail")
    public MethodInvokingJobDetailFactoryBean jobServiceBeanDetail(JobService jobService) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        //是否并发执行
        jobDetail.setConcurrent(false);
        //需要执行的实体bean
        jobDetail.setTargetObject(jobService);
        //需要执行的方法
        jobDetail.setTargetMethod("business");
        return jobDetail;
    }

    //配置简单触发器
    @Bean(name = "simpleTrigger")
    public SimpleTriggerFactoryBean simpleTrigger(JobDetail businessJobDetail) {//businessJobDetail
        SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
        trigger.setJobDetail(businessJobDetail);
        //设置启动延迟
        trigger.setStartDelay(0);
        //每隔5s执行一次
        trigger.setRepeatInterval(5000);
        return trigger;
    }

    //配置cron触发器
    @Bean(name = "cronTrigger")
    public CronTriggerFactoryBean cronTrigger(JobDetail jobServiceBeanDetail) {//目标任务jobServiceBeanDetail
        CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
        triggerFactoryBean.setJobDetail(jobServiceBeanDetail);
        //每隔6s执行一次
        triggerFactoryBean.setCronExpression("0/6 * * * * ?");
        return triggerFactoryBean;
    }

    //配置调用工厂,将所有的触发器引入
    @Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger cronTrigger, Trigger simpleTrigger) {//需要管理的触发器cronTrigger,simpleTrigger
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        //延迟1s启动
        bean.setStartupDelay(1);
        //注册触发器,可以注册多个
        bean.setTriggers(cronTrigger, simpleTrigger);
        return bean;
    }
}

4.3 查看打印

在这里插入图片描述
在这里插入图片描述

spring task

  • Spring 从 3.0 开始增加了自己的任务调度器,它是通过扩展 java.util.concurrent 包下面的类来实现的。使用 spring task 非常简单,只需要给定时任务类添加@Component 和 @EnableScheduling注解,给任务方法添加@Scheduled注解,并让 Spring 扫描到该类即可。

1. 创建任务TaskService

package com.example.demo.schedule;

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

import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@EnableScheduling
public class TaskService {

    //每隔1s执行一次
    @Scheduled(fixedRate = 1000)
    public void fixMethod() {
        try {
            log.info("fixMethod,thread:{}",Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //前一个任务执行完5s后执行
    @Scheduled(fixedDelay = 5000)
    public void delayMethod() {
        log.info("delayMethod,thread:{}",Thread.currentThread().getName());
    }

    //每隔10s执行一次
    @Scheduled(cron = "0/10 * * * * ?")
    public void cronMethod() {
        log.info("cronMethod,thread:{}",Thread.currentThread().getName());
    }

}
  • 启动项目,查看打印
  • fixMethod方法没有每隔1s执行一次;
  • delayMethod方法没有在前一个delayMethod方法执行后5s执行;
  • cronMethod方法也没有每隔10s执行一次;
  • 通过打印可以看出定时任务只有scheduling-1一个线程在执行,属于单线程,fixMethod方法执行时间是2s,即使设置时间间隔是1s,后一次执行也要等到前一次代码执行完才能执行,属于同步执行

在这里插入图片描述

2. 多线程配置

  • 当前项目中存在多个任务时,可以配置 executor 线程池,这里 executor 的含义和 java.util.concurrent.Executor 是一样的,pool-size 的大小官方推荐为 5~10。
  • 创建配置类ScheduleConfig,实现SchedulingConfigurer接口

在这里插入图片描述

package com.example.demo.config;

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

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    //配置线程池--触发器和任务共用
    @Bean
    public Executor taskExecutor(){
        return Executors.newScheduledThreadPool(10);
    }
}

  • 查看打印
  • 线程池内有10个线程,即使任务多了也不影响线程之间的执行时间间隔。

在这里插入图片描述

3. 异步

  • 配置类中添加注解@EnableAsync

在这里插入图片描述

  • 任务类在方法上添加注解@Async

在这里插入图片描述

  • 查看打印

在这里插入图片描述

代码下载地址

https://gitee.com/fisher3652/exportDemo

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值