简介

文章中代码案例曾经同步到码云:代码中的schedule-demo中。

定时工作是指调度程序在指定的工夫或周期触发执行的工作
应用场景:发送邮件、统计、状态批改、音讯推送、流动开启、增量索引

现有的定时工作技术

  1. Java自带的java.util.Timer类,这个类容许你调度一个java.util.TimerTask工作。应用这种形式能够让你的程序依照某一个频度执行,但不能在指定工夫运行。应用较少。(不举荐应用,代码案例中曾经给出阐明)
  2. Spring3.0当前自主开发的定时工作工具spring task,应用简略,反对线程池,能够高效解决许多不同的定时工作,除spring相干的包外不须要额定的包,反对注解和配置文件两种模式。 不能解决过于简单的工作
  3. 业余的定时框架quartz,功能强大,能够让你的程序在指定工夫执行,也能够依照某一个频度执行,反对数据库、监听器、插件、集群

代码实例

1.Timer

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;/** * @Author: njitzyd * @Date: 2021/1/14 22:27 * @Description: Java自带的Timer类 * @Version 1.0.0 */public class MyTimer {    public static void main(String[] args) {//        多线程并行处理定时工作时,Timer运行多个TimeTask时,只有其中之一没有捕捉抛出的异样,其它工作便会主动终止运行,应用ScheduledExecutorService则没有这个问题。////        //org.apache.commons.lang3.concurrent.BasicThreadFactory//        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,//                new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());//        executorService.scheduleAtFixedRate(new Runnable() {//            @Override//            public void run() {//                //do something//            }//        },initialDelay,period, TimeUnit.HOURS);        try {            // 创立定时器            Timer timer = new Timer();            // 增加调度工作            // 安顿指定的工作在指定的工夫开始进行反复的 固定提早执行            timer.schedule(new MyTask(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-01-14 22:43:10"),10*1000);            // 安顿指定的工作在指定的提早后开始进行反复的 固定速率执行            //timer.scheduleAtFixedRate(new MyTask(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-01-14 22:43:10"),10*1000);        } catch (ParseException e) {            e.printStackTrace();        }    }}/** * 自定义的工作类 */class MyTask extends TimerTask {    // 定义调度工作    public void run() {        System.out.println("log2:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));    }}

2.Spring Task

配置有两种形式,一种是基于注解,一种是基于配置文件。在springboot中举荐应用注解和配置类的形式,这里咱们次要应用注解和配置类,基于配置文件的也会给出demo。

  • 基于注解

在springboot的启动类上通过注解@EnableScheduling开启。而后在类的办法上通过@Scheduled注解应用,代码案例如下:

@Componentpublic class ScheduleTest {    @Scheduled(fixedDelayString = "5000")   public void testFixedDelayString() {        System.out.println("Execute at " + System.currentTimeMillis());    }}

具体的应用能够参考我的另一篇博客:@shcedule注解的应用

  • 基于xml配置

首先是工作类:

/** * 工作类 * @author 朱友德 */public class SpringTask {    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    public void m1(){        System.out.println("m1:"+simpleDateFormat.format(new Date()));    }    public void m2(){        System.out.println("m2:"+simpleDateFormat.format(new Date()));    }    public void m3(){        System.out.println("m2:"+simpleDateFormat.format(new Date()));    }}

而后是xml配置:

<!--spring-task.xml配置--><bean id="springTask" class="com.njit.springtask.SpringTask"></bean>   <!--注册调度工作-->   <task:scheduled-tasks>       <!--提早8秒 执行工作-->       <!--<task:scheduled ref="springTask" method="m1" fixed-delay="8000" />-->       <!--固定速度5秒 执行工作-->       <!--<task:scheduled ref="springTask" method="m2" fixed-rate="5000"/>-->       <!--           应用cron表达式 指定触发工夫           spring task 只反对6位的cron表达式 秒 分 时 日 月 星期       -->       <task:scheduled ref="springTask" method="m3" cron="50-59 * * ? * *"/>   </task:scheduled-tasks>   <!--执行器配置-->   <task:executor id="threadPoolTaskExecutor" pool-size="10" keep-alive="5"></task:executor>   <!--调度器配置-->   <task:scheduler id="threadPoolTaskScheduler" pool-size="10"></task:scheduler>

3.quartz

首先咱们要理解一下quartz中的一些基本概念:

  1. Scheduler:任务调度器,是理论执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。
  2. Trigger:触发器,用于定义任务调度的工夫规定,有SimpleTrigger,CronTrigger,DateIntervalTrigger等,其中CronTrigger用的比拟多,本文次要介绍这种形式。CronTrigger在spring中封装在CronTriggerFactoryBean中。

    • SimpleTrigger:简略触发器,从某个工夫开始,每隔多少工夫触发,反复多少次。
    • CronTrigger:应用cron表达式定义触发的工夫规定,如"0 0 0,2,4 1/1 ? " 示意每天的0,2,4点触发。
    • DailyTimeIntervalTrigger:每天中的一个时间段,每N个工夫单元触发,工夫单元能够是毫秒,秒,分,小时
    • CalendarIntervalTrigger:每N个工夫单元触发,工夫单元能够是毫秒,秒,分,小时,日,月,年。
  3. Calendar:它是一些日历特定工夫点的汇合。一个trigger能够蕴含多个Calendar,以便排除或蕴含某些工夫点。
  4. JobDetail:用来形容Job实现类及其它相干的动态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只须要执行某个类的某个办法,就能够通过MethodInvokingJobDetailFactoryBean来调用。
  5. Job:是一个接口,只有一个办法void execute(JobExecutionContext context),开发者实现该接口定义运行工作,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保留在JobDataMap实例中。实现Job接口的工作,默认是无状态的,若要将Job设置成有状态的(即是否反对并发),在quartz中是给实现的Job增加@DisallowConcurrentExecution注解
Quartz 任务调度的外围元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据, scheduler 是理论执行调度的控制器。

在 Quartz 中,trigger 是用于定义调度工夫的元素,即依照什么工夫规定去执行工作。Quartz 中次要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DailyTimeIntervalTrigger,和 CalendarIntervalTrigger

在 Quartz 中,job 用于示意被调度的工作。次要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的工作被执行完之后,能力触发下一次执行。Job 次要有两种属性:volatility 和 durability,其中 volatility 示意工作是否被长久化到数据库存储,而 durability 示意在没有 trigger 关联的时候工作是否被保留。两者都是在值为 true 的时候工作被长久化或保留。一个 job 能够被多个 trigger 关联,然而一个 trigger 只能关联一个 job

  • 引入starter依赖
  <!-- quartz -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-quartz</artifactId>        </dependency>
  • 编写两个工作Task
/** * @author  * 工作一 */public class TestTask1 extends QuartzJobBean{    @Override    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        System.out.println("TestQuartz01----" + sdf.format(new Date()));    }}/** * 工作二 * @author  */public class TestTask2 extends QuartzJobBean{    @Override    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        System.out.println("TestQuartz02----" + sdf.format(new Date()));    }}
  • 编写配置类
/** * quartz的配置类 */@Configurationpublic class QuartzConfig {    @Bean    public JobDetail testQuartz1() {        return JobBuilder.newJob(TestTask1.class).withIdentity("testTask1").storeDurably().build();    }    @Bean    public Trigger testQuartzTrigger1() {        //5秒执行一次        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()                .withIntervalInSeconds(5)                .repeatForever();        return TriggerBuilder.newTrigger().forJob(testQuartz1())                .withIdentity("testTask1")                .withSchedule(scheduleBuilder)                .build();    }    @Bean    public JobDetail testQuartz2() {        return JobBuilder.newJob(TestTask2.class).withIdentity("testTask2").storeDurably().build();    }    @Bean    public Trigger testQuartzTrigger2() {        //cron形式,每隔5秒执行一次        return TriggerBuilder.newTrigger().forJob(testQuartz2())                .withIdentity("testTask2")                .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))                .build();    }}
  • 启动我的项目察看

能够失常的看到工作失常启动,工作Task被执行:

实现原理

1.Timer

简略来说就是执行时把Task放到队列中,而后有个线程(留神他是单线程的,如果执行多个Task,一个抛出异样就会导致整个都蹦)会去拉取最近的工作(队列中是依据下次执行工夫进行排序)去执行,如果工夫没到则wait()办法期待。

ScheduledThreadPoolExecutor的执行步骤是,执行时向队列中增加一条工作,队列外部依据执行工夫程序进行了排序。而后线程池中的线程来获取要执行的工作,如果工作还没到执行工夫就在这等,等到工作能够执行,而后获取到ScheduledFutureTask执行,执行后批改下次的执行工夫,再增加到队列中去。

ScheduledThreadPoolExecutor的运行机制

Timer的应用以及执行原理

2.spring task

在springboot中,应用`@schedule注解默认是单线程的,多个工作执行起来工夫会有问题:B工作会因为A工作执行起来须要20S而被延后20S执行。所以咱们有两个计划去解决这个问题

  • 在办法上应用@Async注解
  • 指定线程池

这里次要介绍第二种,只须要配置一个配置类即可:

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

上面介绍原理:

jdk的线程池和工作调用器别离由ExecutorService、ScheduledExecutorService定义,继承关系如下:

ThreadPoolExecutor:ExecutorService的实现类,其构造函数提供了灵便的参数配置,可结构多种类型的线程池

ScheduledThreadPoolExecutor:ScheduledExecutorService的实现类,用于任务调度

spring task对定时工作的两个形象:

  • TaskExecutor:与jdk中Executor雷同,引入的目标是为定时工作的执行提供线程池的反对,如果设置,默认只有一个线程。
  • TaskScheduler:提供定时工作反对,须要传入一个Runnable的工作做为参数,并指定须要周期执行的工夫或者触发器,这样Runnable工作就能够周期性执行了。

继承关系如下:

工作执行器与调度器的实现类别离为ThreadPoolTaskExecutor、ThreadPoolTaskScheduler

TaskScheduler须要传入一个Runnable的工作做为参数,并指定须要周期执行的工夫或者触发器(Trigger)。

spring定义了Trigger接口的实现类CronTrigger,反对应用cron表达式指定定时策略,应用如下:

scheduler.schedule(task, new CronTrigger("30 * * * * ?"));

在springboot我的项目中,咱们个别都是应用@schedule注解来应用spring task,这个注解外部的实现就是应用下面的内容。

spring在初始化bean后,通过postProcessAfterInitialization拦挡到所有的用到@Scheduled注解的办法,并解析相应的的注解参数,放入“定时工作列表”期待后续解决;之后再“定时工作列表”中对立执行相应的定时工作(工作为程序执行,先执行cron,之后再执行fixedRate)

源码解析

3.quartz

原理参考这篇文章:

quartz原理

参考

为什么应用ScheduledThreaedPoolExcutor而不是Timer

quartz原理解析

quartz原理