美文网首页
09- QuartZ

09- QuartZ

作者: XAbo | 来源:发表于2022-05-20 17:03 被阅读0次

Java中实现定时任务的几种方式:

  • Timer: java.util.Timer, 一个JDK自带的处理简单的定时任务的工具。
  • ScheduledExecutorService: java.util.concurrent.ScheduledExecutorService, JDK中的定时任务接口,可以将定时任务与线程池结合使用。
  • Sceduled: org.springframework.scheduling.annotation.Scheduled, Spring框架中基于注解来实现定时任务处理方式。
  • Quartz: 支持分布式调度任务的开源框架。

Spring Task 与 Quartz 对比:

  • Quartz提供了完整的分布式并发支持,Spring task的话还是需要自己手动配置线程池以及借助其他分布式工具实现。
  • Quartz支持调度信息持久化,可以去Quartz官方网站下载建表sql,然后配置数据源等信息实现持久化, Spring task不支持持久化。
  • Spring Task 使用起来非常简单Quartz还需要写其他配置相对来说比Spring Task复杂点。

需求简单的话可以直接使用 Spring Task ,比如一个简单的数据对接接口。如果会有复杂的业务需求不如一步到位,直接使Quartz。

一、Timer

Timer的schedulescheduleAtFixedRate在任务计划时间和实际开始时间没有异常的情况下,当任务的执行时间超过设置的执行间隔,会丢失任务。schedule不受异常时间的控制。

public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        Calendar c = Calendar.getInstance();
        c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
        Date planTime = c.getTime();
        System.out.println("计划执行时间" + planTime);
        System.out.println("实际执行时间" + new Date());
        t.schedule(task, planTime, 3000);
    }
}

class FooTimeTask extends TimerTask {
    private String name;
    public FooTimeTask(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           // Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任务开始时间不正常-任务执行2S-设置间隔时间3S-时间间隔时间3S 任务开始时间不正常-任务执行4S-设置间隔时间3S-实际间隔时间4S
public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        t.scheduleAtFixedRate(task, new Date(), 3000);
    }
}
class FooTimeTask extends TimerTask {
    private String name;

    public FooTimeTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           //Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任务开始时间正常-任务执行2S-设置间隔时间3S-实际间隔时间3S 任务开始时间正常-任务执行4S-设置间隔时间3S-时间间隔时间4S
Timer的scheduleAtFixedRate在任务开始时间不正常情况下与schedule有区别:存在时间追赶。
public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        Calendar c = Calendar.getInstance();
        c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
        Date planTime = c.getTime();
        System.out.println("计划执行时间" + planTime);
        System.out.println("实际执行时间" + new Date());
        t.scheduleAtFixedRate(task, planTime, 3000);
    }
}

class FooTimeTask extends TimerTask {
    private String name;

    public FooTimeTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           //Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任务开始时间不正常-任务执行2S-设置间隔时间3S-时间间隔时间2S 任务开始时间不正常-任务执行4S-设置间隔时间3S-时间间隔时间4S

二、ScheduledExecutorService

摘抄:java定时任务之Timer和ScheduledExecutorService
Timer的缺陷:
1、执行多个任务的时候,必须第一个执行完后才会执行第二个。
2、timer是单线程执行,因此一个任务抛异常,其它任务也不能执行了。

import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
    private static long start;
    public static void main(String[] args) throws Exception {

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task1 invoked ! "+(System.currentTimeMillis() - start));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! "+ (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    }
}  
结果

newScheduledThreadPool:
1、可以多个线程同时执行不同的任务。
2、因为是多个线程所有,任务抛出异常,不影响其它任务的执行。
scheduleAtFixedRate : 0s后开始执行任务,前一次任务的开始时间和下次任务的开始时间中间间隔是5s , 如果前一次任务执行时间大于5s ,那么下次任务会在队列中等待,直到前一次任务执行完后,再执行。
scheduleWithFixedDelay : 0s后开始执行任务,前一次任务的结束时间和后一次任务的开始时间中间间隔是5s。

public class Scheduled {
    static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    static class Task implements Runnable {
        public void run() {
            try {
                Thread.sleep(2000L);
                System.out.println(Thread.currentThread().getName());
            } catch (Exception e) {

            }
        }
    }
    /**
     * command:执行线程
     * initialDelay:初始化延时
     * period:两次开始执行最小间隔时间
     * delay:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
     * unit:计时单位
     */
    public static void main(String[] args) {
        scheduledExecutorService.scheduleAtFixedRate(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
    }
}

三、QuartZ

可参考:Java定时框架Quartz

  • Quartz是功能强大的开源作业调度库,可以集成到最小的独立应用程序到最大的电子商务程序中。
  • Quartz可以通过创建简单或者复杂的计划来执行成千上万的任务。
  • 任何任务标准的Java组件,都可以执行相应的编程操作。
  • Quartz Scheduler支持JTA事务和集群。
  • Quartz实现了任务和触发器多对多的关系,可以将多个任务和不同的触发器相关联。

Quartz的核心

  • JobBuilder:创建Job和JobDetail。
  • JobDataMap:可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。
  • Job:Job接口中只有一个方法 execute(JobExecutionContext context);表示要执行的具体内容。
    Job的属性有两种: 两者都是在值为true时表示任务被持久化或者保留;一个Job可以关联多个Trigger, 一个Trigger只能关联一个Job
    volatility : 表示任务是否被持久化到数据库存储;
    durability : 没有trigger关联的时候任务是否保留;
  • JobDetail:表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。它本身可能是有状态的。
  • TriggerBuilder:创建Trigger。
  • Trigger:代表一个调度参数的配置。
  • Scheduler:代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。

Quartz设计成JobDetail + Job的原因在于:

  • JobDetail用于定义任务数据, 真正的执行任务的逻辑在Job中。
  • 因为任务有可能是并发执行的,如果Scheduler直接使用Job, 会存在对同一个Job实例并发访问的问题;通过JobDetail绑定Job的方式 ,Scheduler每次执行,都会根据JobDetail创建一个新的Job实例,可以避免并发访问的问题。
Quartz主要线程

Quartz的持久化:https://www.jianshu.com/p/d2f7ad108ab2

Quartz数据存储

3.1 QuartZ入门

//@DisallowConcurrentExecution   禁止并发执行同一个Job定义的多个实例。
//@PersistJobDataAfterExecution 多个Job实例共享JobDataMap;对Trigger中的JobDataMap无效
public class MyJob implements Job {
  //可以在JobDetail和Trigger中给Job中的属性赋值
    private  String  name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        //JobDetail的使用
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
        JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
        System.out.println("MyJob exe " + new Date() +" || "+ jobDataMap.get("job") +" || " + triggerDataMap.get("trigger")+"||"+mergedJobDataMap.get("com") +"||"+name);
        System.out.println("=============");
        //1.scheduler每次执行都会根据jobDetail创建一个新的Job实例。避免并发问题。
        //  这样存在的问题:多个Job无法共享JobDataMap。
        System.out.println("jobDetail"+System.identityHashCode(jobExecutionContext.getJobDetail()));
        System.out.println("job"+System.identityHashCode(jobExecutionContext.getJobInstance()));
        jobDataMap.put("count",jobDataMap.getInt("count")+1);
        System.out.println("计数:"+jobDataMap.getInt("count"));
        //2.默认是并发执行调度内的Job;Scheduler不会理会Job中的任务执行时间,严格按照调度执行。
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
    @Test
    void quartz() throws  Exception {
        int count = 0;
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1","group1")       //定义属性
                .usingJobData("job","myJobDetail")
                .usingJobData("com","jobcom")
                .usingJobData("name","ABO") // 会给job中的属性自动赋值
                .usingJobData("count",count)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1","trigger1") //定义属性
                .usingJobData("trigger","myTrigger")
                .usingJobData("com","triggercom")
                .usingJobData("name","ABO-trigger") // 会给job中的属性自动赋值
                .startNow()
                .withSchedule(                                   //定义作业执行规则
                 SimpleScheduleBuilder.simpleSchedule()  //简单的调度
                 .withIntervalInSeconds(1)                //间隔时间
                 .repeatForever()                          //一直执行
                ).build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        //默认是并发执行调度内的Job;不会理会Job中的任务执行时间,严格按照调度执行。
        scheduler.start();

        //测试使用
        Thread.sleep(10000);
    }
无DisallowConcurrentExecution和@PersistJobDataAfterExecution 有DisallowConcurrentExecution和@PersistJobDataAfterExecution

3.2 Trigger

通用的触发器包括两类:

  • SimpleTrigger: 根据Quartz框架的类中自定义的方法设置定时执行规则。
  • CronTrigger: 基于Cron表达式实现触发器。

misfire:任务错过执行的处理。
优先级:同时触发的Trigger之间优先级高的先执行。
calendar:用于排除时间段。

misfire

错过触发的判断条件

  • job到达触发时间时候没有执行。
  • 被执行的延迟时间超过了Quartz配置的misfireThreshold阈值。

错过触发的产生原因

  • 当job达到触发时间时,所有线程都被其他job占用了。
  • 当job需要触发的时间点,scheduler停止了。
  • job使用了@PersistJobDataAfterExecution。job不能并发执行,当到达下一个job执行点的时候,上一个任务还没有完成。
  • job指定了过去的开始执行时间(在阈值范围内,还可以被正常执行)。

错过触发的应对策略

  • SimpleTrigger:
    nowXXX相关的策略,会立即执行第一个misfire的任务,同时会修改startTime和repeatCount,因此finalFireTime会重新计算,原计划执行时间会被打乱。
    nextXXX相关的策略,不会立即执行misfire的任务,也不会修改startTime和repeatCount,finalFireTime也没有改变,发生了misfire也还是按照原计划进行执行。
  • CronTrigger
    MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:Quartz不会判断发生了misfire,立即执行所有发生了misfire的任务,然后按照原计划进行执行。
    MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立即执行第一个发生misfire的任务,忽略其他发生misfire的任务,按照原计划继续执行。
    MISFIRE_INSTRUCTION_DO_NOTHING:所有发生misfire的任务都被忽略,只是按照计划继续执行。

默认使用MISFIRE_INSTRUCTION_SMART_POLICY策略。

3.3 scheduler

SchedulerFactory:

  • 创建Scheduler。
  • DireSchedulerFactory:在代码中定制Scheduler参数。
  • StdSchedulerFactory:读取类路径下quartz.properties配置实例化Scheduler。

JobStore:

  • 存储运行时的信息,包括:Trigger、Scheduler、JobDetail、业务锁等。
  • RAMJobStore:内存实现。
  • JobStoreTX:JDBC,事务由Quartz管理。
  • JobStoreCMT:JDBC,使用容器事务。
  • ClusteredJobStore:集群实现。
  • TerracottaJobStore:Terracotta中间件。

ThreadPool:

  • SimpleThreadPool。
  • 自定义线程池。

3.4 SpringBoot整合Quartz

1.job类

public class QuartzDemo implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Execute..."+new Date());
    }
}

2.配置类,配置触发器与任务调度器

/**
 * Quartz配置类
 */
@Configuration
public class QuartzConfig {
    /**
     * 1、创建Job对象
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean(){
        JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
        //关联我们自己的Job类
        factoryBean.setJobClass(QuartzDemo.class);
        return factoryBean;
    }
    /**
     * 2、创建Trigger对象
     */
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
        SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
        //关联JobDetail对象
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        //该参数表示一个执行的毫秒数
        factoryBean.setRepeatInterval(2000); //每隔2秒执行一次
        //重复次数
        factoryBean.setRepeatCount(5);
        return factoryBean;
    }
    /**
     * 3、创建Scheduler
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
        SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
        //关联trigger
        factoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
        return factoryBean;
    }
}

3.启动类

@SpringBootApplication
@EnableScheduling
public class QuartzApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(QuartzApplication.class, args);
   }
}

相关文章

网友评论

      本文标题:09- QuartZ

      本文链接:https://www.haomeiwen.com/subject/sfufprtx.html