关于程序员:Java中定时任务TimerSpring-Taskquartz详解

8次阅读

共计 7694 个字符,预计需要花费 20 分钟才能阅读完成。

简介

文章中代码案例曾经同步到码云: 代码中的 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 注解应用,代码案例如下:

@Component
public 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 的配置类
 */
@Configuration
public 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 注解
  • 指定线程池

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

@Configuration
public 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 原理

正文完
 0