java quartz timer_Java中定时任务Timer、Spring Task、quartz详解

简介

文章中代码案例已经同步到码云:代码中的schedule-demo中。定时任务是指调度程序在指定的时间或周期触发执行的任务

使用场景:发送邮件、统计、状态修改、消息推送、活动开启、增量索引

现有的定时任务技术Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。使用较少。(不推荐使用,代码案例中已经给出说明)

Spring3.0以后自主开发的定时任务工具spring task,使用简单,支持线程池,可以高效处理许多不同的定时任务,除spring相关的包外不需要额外的包,支持注解和配置文件两种形式。 不能处理过于复杂的任务

专业的定时框架quartz,功能强大,可以让你的程序在指定时间执行,也可以按照某一个频度执行,支持数据库、监听器、插件、集群

代码实例

1.Timerimport 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配置:

3.quartz

首先我们要了解一下quartz中的一些基本概念:Scheduler:任务调度器,是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。

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个时间单元触发,时间单元可以是毫秒,秒,分,小时,日,月,年。

Calendar:它是一些日历特定时间点的集合。一个trigger可以包含多个Calendar,以便排除或包含某些时间点。

JobDetail:用来描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。

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依赖

org.springframework.boot

spring-boot-starter-quartz

编写两个任务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被执行:

01bcbc2d8b3a9065c1111dd9c93ff00e.png

实现原理

1.Timer

简单来说就是执行时把Task放到队列中,然后有个线程(注意他是单线程的,如果执行多个Task,一个抛出异常就会导致整个都蹦)会去拉取最近的任务(队列中是根据下次执行时间进行排序)去执行,如果时间没到则wait()方法等待。

而ScheduledThreadPoolExecutor的执行步骤是,执行时向队列中添加一条任务,队列内部根据执行时间顺序进行了排序。然后线程池中的线程来获取要执行的任务,如果任务还没到执行时间就在这等,等到任务可以执行,然后获取到ScheduledFutureTask执行,执行后修改下次的执行时间,再添加到队列中去。

ScheduledThreadPoolExecutor的运行机制

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定义,继承关系如下:

2b396bc522a7e75bc370434d6d8cf9f4.png

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

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

spring task对定时任务的两个抽象:TaskExecutor:与jdk中Executor相同,引入的目的是为定时任务的执行提供线程池的支持,如果设置,默认只有一个线程。

TaskScheduler:提供定时任务支持,需要传入一个Runnable的任务做为参数,并指定需要周期执行的时间或者触发器,这样Runnable任务就可以周期性执行了。

继承关系如下:

c587ca98b57f7b4a79bb0a5064ffed39.png

任务执行器与调度器的实现类分别为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

原理参考这篇文章:

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值