定时任务

本文介绍了如何在系统对接中使用定时任务推送历史数据。探讨了Spring Task和Quartz两种定时任务框架,包括它们的时间表达式、核心概念以及应用场景。文中详细解释了cron表达式,并对比了Spring Task的简单实现与Quartz的多线程调度。最后讨论了在多任务场景下,如何利用线程池优化定时任务的执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       今天有系统对接需要使用接口推送历史数据,因为是小程序的后台,不能写一个按钮来调用方法推送历史数据,就想通过定时任务来向其他系统推送历史数据。

      目前定时任务的种类有四种:

名称特点
quartzquartz 是一个开源组织提供的功能强大的开源项目,既能按照一个指定的时间进行简单的调度作业,也能根据一个时间间隔进行循环调度作业,还能把多个作业和多个不同的触发器进行关联,进行复杂的调度作业
spring task是一个相较于quartz轻量的定时任务框架,从spring3.0后spring就把task整合进去,如果使用的话不需要额外引入jar包,springboot项目下可以很简单的实现大部分的业务需求,并且实现起来很简单,缺点下面会涉及到
timerjava中自带的定时任务,缺点比多
scheduledExecutorServicejava1.5后新增的定时任务,进行简单的任务调度,跟timer相比推荐使用这一个

   要了解的是

              1 每种定时任务支持做什么

              2定时任务中的cron 的写法和意义

 

 

        cron表达式详解:

       cron表达式是一个控制触发调度任务时间的字符串,这个表达式中是有至少6(最多7个)个时间域,并且时间域之间用空格隔开。为什么会有至少6个时间域最多7个时间域这种说法呢?第一不同的定时允许的cron表达式不同,spring task的cron允许的如时间域就是6个,没有年的时间域,如果不为6个数量会报错(Cron expression must consist of 6),而quartz是七个时间域;第二

quartz的七个时间域中的年可以省略不写。

          由左03向右时间域代表的意义:

                       

时间域含义允许出现字符有效范围
seconds“, - * /”四个特殊字符0-59的整数
minutes“, - * /”四个特殊字符0-59的整数
hours小时“, - * /”四个特殊字符0-59的整数
dayofMonth一个月中第几天“, - * / ? L W C”八个特殊字符0-31整数
month“, - * /”四个特殊字符1-12或JAN-DEC
dayofweek一周内第几天",- * / ? L C #"八个字符1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一
year年(spring task不存在,quartz可以省略不写)“, - * /”四个特殊字符1970-2099

 

                     特殊字符的含义:

                       

符号意义使用域
*在该时间域匹配任何值所有时间域
枚举值逗号前后的值都会执行所有时间域
/开始时间开始出发,然后每隔/后时间触发一次 
-表示一个时间范围所有时间域

不确定的值,因为月中日,和型其中日可能冲突,所以用?

0 0 0 1 * * 每天都触发

0 0 0 1 * ? 每月1号触发

只在dayOfMonth 和dayOfWeek使用
L表示最后,如果在星期域上使用5L表示最后一个周四触发只在dayOfMonth 和dayOfWeek使用
W表示工作日,周一到周五,如果dayOfMonth写的是6w,假如6日是工作日则触发,如果6日是周六则在5日(周五触发),如果6日是周日,则7日触发(周一)只在dayOfMonth使用
LW表示一个月的最后一个工作日(周五)只在dayOfMonth使用
#用于确定每个月的第几个星期几,例如4#2 表示某月的第二个星期三只在dayOfMonth使用

 

 

      在工作中发现springboot 使用springtask 特别简单,但是发现他的时间域只有6个,不存在年的时间域,也就是意味着使用spring task的话不能指定时间触发一次这种定时任务。

     quartz:

      quartz有几个核心的概念:scheduler,trigger,job,jobDetail,其中trigger和job和jobDetail是元数据,scheduler为实际进行任务调度的控制器。

        trigger    : 触发器,用于定义调度任务的时间规则,quartz的触发有四种,,simpleTrigger,cronTriggerDailyTimeIntervalTrigger,CalendarIntervalTrigger。但是simpleTrigger和cronTrigger是最常用的。

      job &jobDetail : quartz  把定时任务分成两部分,其中job是来定义任务的执行逻辑,jobDetail是用来描述job的定义(例如job接口的实现类以及其他相关的静态信息)。quartz主要有两种类型的job,stateLessJob和stateFulJob

      scheduler: 实际执行逻辑调度的控制器,quartz提供了两种调度器的工厂类 DirectSchedulerFactory和StdSchedulerFactory等工厂类,用于支持scheduler相关对象的产生。StdSchedulerFactory是对org.quartz.SchedulerFactory接口的一个实现。是使用一套属性(NameValueCollection)来创建和初始化Quartz Scheduler。这些属性通常在文件中存储和加载。也可以通过编写程序来直接操作工厂。简单的调用工厂的getScheduler()就可以产生一个scheduler,初始化(以及它的ThreadPool、jobStore 和DataSources),并返回一个公共的接口。      DirectSchedulerFactory是SchedulerFactory的另一个实现,使用它不允许使用声名式配置,是使用硬编码的方式设置scheduler,所以不推荐。

          

   

下面盗用两个比较重要的时序图:

  

springboot2.x版本已经把quartz添加入框架,不需要单独引入jar包。但是我们项目上还没有使用这么高的springboot版本,我就需要引入一个jar包。

   

     simpleTrigger是Trigger接口的一个实现,它可以触发一个已经安排调度程序的任务,可以触发一个固定时间的定时任务,也可以指定时间间隔重复执行该任务。simpleTrigger包含几个特点:开始时间、结束时间、重复次数以及重复执行的时间间隔。simpleTrigger 实例创建依赖于TriggerBuilder和SimpleScheduleBuilder。

  下面是我的pom.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.test</groupId>
	<artifactId>job</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>job</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--必须添加,要不然会出错,项目无法启动 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
		</dependency>

		<!--引入quartz相关依赖 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.2.1</version>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-api</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

我的任务类

@Component
@EnableScheduling
public class MyJob {

    /*
     *  方法1
     */

    public void  firstJob(){
        System.out.println(Thread.currentThread().getName()+"the firstJob is running,the time is"+System.currentTimeMillis());
    }

    /*
     * 方法2
     */
    public void secJob(){
        System.out.println(Thread.currentThread().getName()+"the secJob is running,the time is"+System.currentTimeMillis());
    }
}

先使用simpleTrigger

@Configuration
public class QuartzConfig {

    //创建一个jobDetail
    @Bean(name="simpleJobDetail")
    public MethodInvokingJobDetailFactoryBean simpleJobDetail(MyJob myJob){
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        //设置是否并发执行
        jobDetail.setConcurrent(false);
        //设置要执行定时任务的对象
        jobDetail.setTargetObject(myJob);
        //需要执行的方法
        jobDetail.setTargetMethod("firstJob");
        return jobDetail;
    }

    //创建一个simple
    @Bean(name="simpleTrigger")
    public SimpleTriggerFactoryBean simpleTrigger(JobDetail simpleJobDetail){
        SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
        trigger.setJobDetail(simpleJobDetail);
        trigger.setStartDelay(0);
        trigger.setRepeatInterval(10000);
        return trigger;
    }


    //创建一个schedule
    @Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger simpleTrigger){
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setStartupDelay(5);
        bean.setTriggers(simpleTrigger);
        return bean;
    }

}

  执行后控制台显示为

    

   程序启动后定时任务延时5s启动,并且可以看出quartz是多线程启动的,并且默认核心线程是10个。

  两种quartz的定时人任务

  

@Configuration
public class QuartzConfig {
     /******************************simple模式开始*******************************/
    //创建一个jobDetail
    @Bean(name="simpleJobDetail")
    public MethodInvokingJobDetailFactoryBean simpleJobDetail(MyJob myJob){
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        //设置是否并发执行
        jobDetail.setConcurrent(false);
        //设置要执行定时任务的对象
        jobDetail.setTargetObject(myJob);
        //需要执行的方法
        jobDetail.setTargetMethod("firstJob");
        return jobDetail;
    }

    //创建一个simple
    @Bean(name="simpleTrigger")
    public SimpleTriggerFactoryBean simpleTrigger(JobDetail simpleJobDetail){
        SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
        trigger.setJobDetail(simpleJobDetail);
        trigger.setStartDelay(0);
        trigger.setRepeatInterval(1000);
        return trigger;
    }

    /******************************simple模式结束************************************/

    /******************************cron模式开始***********************************/
    //创建一个cronJobDetail
    @Bean(name="cronJobDetail")
    public  MethodInvokingJobDetailFactoryBean cronJobDetail(MyJob myJob){
        MethodInvokingJobDetailFactoryBean jobDetail =  new MethodInvokingJobDetailFactoryBean();
        jobDetail.setConcurrent(false);
        jobDetail.setTargetObject(myJob);
        jobDetail.setTargetMethod("secJob");
        return jobDetail;
    }

    @Bean(name="cronTrigger")
    public CronTriggerFactoryBean cronTrigger(JobDetail cronJobDetail){
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(cronJobDetail);
        trigger.setCronExpression("0/5 * * * * ? *");
        return trigger;
    }
    /*******************************cron模式结束*****************************************/


    //创建一个schedule
    /*@Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger simpleTrigger){
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setStartupDelay(5);
        bean.setTriggers(simpleTrigger);
        return bean;
    }*/

    @Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger simpleTrigger,Trigger cronTrigger){
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setStartupDelay(5); //如果不设置,扫描到这个配置就会触发定时任务
        bean.setTriggers(simpleTrigger,cronTrigger);
        return bean;
    }
}

  当把另个jobDetail 都设置为不并发执行时jobDetail.setConcurrent(false);,两个定时任务不会并发执行,定时任务会等待另一定时任务执行结束才会执行。如下:

 

spring task:

  使用spring task 很简单,只需要在启动类上加上 @EnableScheduling 开始定时任务,然后新建任务类,并且能够让spring扫描到。

@SpringBootApplication
@EnableScheduling
public class JobApplication {

	public static void main(String[] args) {
		SpringApplication.run(JobApplication.class, args);
	}
}
/*
 *spring task 定时任务demo
 */

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class TaskDemo {

    @Scheduled(cron="0/5 * * * * *")
    public void testScheduler1(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务的名字是testScheduler1");
    }
    @Scheduled(cron="0/5 * * * * *")
    public void testScheduler2(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务的名字是testScheduler2");
    }

}

   上面是一个最简单spring task的定时任务,可以看出两个定时任务会走同一个线程。如果定时任务少影响不大,但是如果有一个定时任务执行过程很长,会影响下面线程的执行。

     

public class TaskDemo {

    @Scheduled(cron="0/5 * * * * *")
    public void testScheduler1(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务的名字是testScheduler1,执行时间是"+System.currentTimeMillis());
        try {
            Thread.currentThread().sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Scheduled(cron="0/5 * * * * *")
    public void testScheduler2(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务的名字是testScheduler2,执行时间是"+System.currentTimeMillis());
    }

}

线程会一直等待线程的锁被释放后才会继续执行。

@Component
public class TaskDemo2 {

   // @Scheduled(cron="0/5 * * * * *")
    public void task1(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***cron****的名字是task1,执行时间是"+System.currentTimeMillis());
    }

    @Scheduled(fixedRate = 5000)
    public void task2(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***fixedRate****的名字是task2,执行时间是"+System.currentTimeMillis());
    }

    //@Scheduled(fixedDelay = 5000)
    public void task3(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***fixedDelay****的名字是task3,执行时间是"+System.currentTimeMillis());
    }

单独执行 fixedRate和fixedDelay的执行结果是:

        fixedRate:是按照一个固定的时间间隔进行执行的,任务执行时间间隔是从上一次方法开始执行算起,如果上一次方法阻塞了,下一次也不会执行,但是在阻塞这段时间内累计应执行的次数,当不再阻塞时,全部执行,而后再按照固定频率执行。

        fixedDelay:间隔是前次任务的结束与下次任务的开始之间的时间间隔,如果上一次方法阻塞住了,那么直到上一次执行完,并过完指定的时间间隔才会执行下一次。

     在实际生产中,定时任务比较多的话,单线程的方式肯定是不行的。要使用线程池来使用定时任务。

 

###############线程池的配置###############
thread:
   corePoolSize: 10   #核心线程数
   maxPoolSize: 200   #最大线程数
   queueCapacity: 10  #队列容量

         corePoolSize :核心线程数会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有空闲线程,线程池也会优先新线程处理,设置allowCoreThreadTimeout=true(默认为false)时,核心线程会超时关闭

        queueCapacity:队列任务容量(阻塞队列),当核心线程达到最大时,新任务会放在队列中排队等待执行

          maxPoolSize:最大线程数。当线程数>corePoolSize且任务队列已满时。线程池会创建新的线程处理任务,当线程数=maxPoolSize且任务队列已满,线程池会拒绝任务而抛出异常

/*
 * 线程池的配置文件
 */
@Configuration
@EnableAsync
public class AnsyConfig {
   Logger logger = LoggerFactory.getLogger(AnsyConfig.class);
    @Value("${thread.corePoolSize}")
    private int corePoolSize;
    @Value("${thread.maxPoolSize}")
    private int maxPoolSize;
    @Value("${thread.queueCapacity}")
    private int queueCapacity;

    @Bean
    public Executor taskExecutor(){
        ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.initialize();
        return executor;
    }
}

 

@Component
@Async  //新增注解
public class TaskDemo2 {


    @Scheduled(cron="0/5 * * * * *")
    public void task1(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***cron***********的名字是task1,执行时间是"+System.currentTimeMillis());
    }

    @Scheduled(fixedRate = 5000)
    public void task2(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***fixedRate******的名字是task2,执行时间是"+System.currentTimeMillis());
    }

    @Scheduled(fixedDelay = 5000)
    public void task3(){
        System.out.println(Thread.currentThread().getName()+"正在执行任务***fixedDelay****的名字是task3,执行时间是"+System.currentTimeMillis());
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值