• quartz学习笔记


    1. quartz组件

    在这里插入图片描述

    (1) Job

    编写Job类 (任务类),实现Job接口,重写exeute方法,此方法就是要执行的任务,类似于Runable

    public class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("任务正在执行 - "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2) JobDetail

    JobDetail是对Job的封装可以设置许多属性,还包括一个比较重要的JobDataMap,用来存储Job实例的状态信息

    调度器需要借助JobDetail对象来添加Job实例

    JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
            .withIdentity("任务1","组1")
            .build();
    
    • 1
    • 2
    • 3

    (3) trigger

    触发器用来告诉调度程序任务什么时候触发,框架提供了5种触发器类型,但两个最常用的SimpleTrigger和CronTrigger

    • SimpleTrigger:执行N次,重复N次
    • CronTrigger:几秒 几分 几时 哪日 哪月 哪周 哪年,执行
    //触发器
    Trigger trigger = TriggerBuilder.newTrigger()
               .withIdentity("触发器1","组1")
               //触发策略  可以设置是间隔执行,还是到某个时间执行
               //此处为间隔1秒执行一次,一直执行
               .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
               .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    trigger的优先级
    • 优先级数字越大触发器优先级越高,在触发器并发执行情况下,优先级越高的触发器执行越靠前

    • 若触发器优先级未设置,那么优先级最低

      Trigger trigger = TriggerBuilder.newTrigger()
              //设置trigger的优先级
              .withPriority(10)
              .withIdentity("触发器1","组1")
              .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
              .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (4) Scheduler

    Scheduler称之为调度器,用于管理触发器和Job,例如启动某一任务,暂停某一任务等职责。

    Job会被注册进Scheduler中,Scheduler通过调用Trigger来执行

    //调度器
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    //将job与trigger都放入调度器中
    scheduler.scheduleJob(jobDetail, trigger);
    //启动定时任务
    scheduler.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    调度器的几种行为

    • start 开始
    • stop 停止
    • pause暂停
    • resume重试

    (5) JobExecuteContext

    看名字就知道,这是任务执行时的上下文,在实现Job方法重写excute时,作为参数传入任务中

    public class MyJob implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            //从context中获取job与trigger的名称  
            System.out.println("任务名为:"+context.getJobDetail().getKey()+"---触发器名为:"+context.getTrigger().getKey().getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 当scheduler调用一个job,就会将jobExecuteContext传递给job的execute方法

    • job能够通过该对象访问到quartz运行时候的环境以及job本身的明细数据

    (6) jobDataMap

    jobDataMap用于存储Job实例的状态信息,在excute执行中,可以通过JobExecuteContext去获取map中的值

      JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withIdentity("任务1","组1")
                    //将map信息存放进jobDataMap中
                    .usingJobData("jobKey","jobValue")
                    .build();
            
      Trigger trigger = TriggerBuilder.newTrigger()
              .withIdentity("触发器1","组1")
              .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
              //将map信息存放进jobDataMap中
              .usingJobData("triggerKey","triggerValue")
              .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public class MyJob implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            //从context中获取JobDataMap存储的信息
            System.out.println(context.getJobDetail().getJobDataMap().get("jobKey"));
            System.out.println(context.getTrigger().getJobDataMap().get("triggerKey"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2. Job的并发执行

    在Job任务每次执行时,JobDetail都会生成一个新的实例(包括内部的Job),例如间隔一秒执行一次任务,那么间隔一秒就会生成一个新的jobdetail实例

    这是为了规避并发访问的问题,例比如一个任务要执行10秒,而调度算法是每秒钟触发1次,创建新的实例就是为了使每次调度都能执行,此时就有可能多个任务被并发执行

    取消任务的并发执行

    • @DisallowConcurrentExecution 禁止并发地执行同一个JobDetail
    • @PersistJobDataAfterExecution 使JobDataMap跟随每一个任务执行而变动 (不使用的情况下新建jobdetail该Map也会新建)

    这两个注解一般搭配使用

    @DisallowConcurrentExecution
    @PersistJobDataAfterExecution
    public class MyJob implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            //用户判断是否是同一个对象
            System.out.println("jobDetail:"+System.identityHashCode(context.getJobDetail()));
            System.out.println("job:"+System.identityHashCode(context.getJobInstance()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意点: @DisallowConcurrentExecution是对JobDetail实例生效,也如果定义两个JobDetail,引用一个Job类,是可以并发执行的

    3. Quartz的线程池

    定时器要调度多个定时任务,就得有一个线程池来进行任务的并发处理

    当执行schedulerFactory.getScheduler()时,会初始化一个线程池SimpleThreadPool

    在这里插入图片描述
    在源码中,默认创建的线程池是一个SimpleThreadPool

    • 此SimpleThreadPool是一个比较简单的线程池实现,只有线程数这一个属性,不像其他功能比较丰富的线程池有像核心线程数、最大线程数、队列大小等参数

    • 此SimpleThreadPool的默认线程数为10

    修改quartz线程池 的线程数量

    可以在配置文件或配置类中进行配置

     //代码配置
     Properties prop = new Properties();
     // 线程池配置
     prop.put("org.quartz.threadPool.threadCount", "20");
     SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
     Scheduler scheduler = schedulerFactory.getScheduler();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4. 任务错过触发的情况

    列出了以下几种常见的情况

    • 当job达到触发时间时,所有线程都被其他job占用,没有可用线程

    • 在job需要触发的时间点,scheduler停止了(可能是手动调用pasue等方法,但也可能是意外停止的)

    • job使用了@ DisallowConcurrentExecution注解,job不能并发执行,当达到下一个job执行点的时候,上一个任务还没有完成

    • job指定了过去的开始执行时间,例如当前时间是8点00分0O秒,指定开始时间为7点00分00秒

    任务错过的解决措施

    (1) 增加线程池数,防止线程数不够的情况
    (2) 设置任务执行的阈值,在到时间点后一段时间内仍可以执行

      Properties prop = new Properties();
      //1.线程池配置
      prop.put("org.quartz.threadPool.threadCount", "20");
      //2.设置任务执行时间阈值  单位是毫秒
      prop.put("org.quartz.jobStore.misfireThreshold", "10000");
      SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
      Scheduler scheduler = schedulerFactory.getScheduler();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (3) 设置错过的处理策略

    具体的处理策略有很多,实际使用时可以自行百度

     Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("触发器1", "组1")
                    .withSchedule(
                            SimpleScheduleBuilder.simpleSchedule()
                                    .withIntervalInSeconds(3)
                                    .repeatForever()
                                    // 设置错失触发后的调度策略 
                                    .withMisfireHandlingInstructionNowWithRemainingCount()
                    )
                    .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5. SpringBoot整合quartz

    maven依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-quartzartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    简单使用方式

    (1) 启动类上添加@EnableScheduling
    在这里插入图片描述
    (2) 配置定时任务@Scheduled
    在这里插入图片描述

    配置方式使用

    (1) quartz配置

    默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件

    在这里插入图片描述
    (2) quartz 配置类

    /**
     * @author ruoxi
     * @createTime 2022/9/14 16:41
     */
    @Configuration
    public class QuartzConfig implements SchedulerFactoryBeanCustomizer {
    
        @Bean
        public Properties properties() throws IOException {
            PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
            // 对quartz.properties文件进行读取
            propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
            // 在quartz.properties中的属性被读取并注入后再初始化对象
            propertiesFactoryBean.afterPropertiesSet();
            return propertiesFactoryBean.getObject();
        }
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            schedulerFactoryBean.setQuartzProperties(properties());
            return schedulerFactoryBean;
        }
    
        /**
         * quartz初始化监听器
         */
        @Bean
        public QuartzInitializerListener executorListener() {
            return new QuartzInitializerListener();
        }
    
        /**
         * 通过SchedulerFactoryBean获取Scheduler的实例
         */
        @Bean
        public Scheduler scheduler() throws IOException {
            return schedulerFactoryBean().getScheduler();
        }
    
        /**
         * 使用阿里的druid作为数据库连接池
         */
        @Override
        public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) {
            schedulerFactoryBean.setStartupDelay(2);
            schedulerFactoryBean.setAutoStartup(true);
            schedulerFactoryBean.setOverwriteExistingJobs(true);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    (3)使用

    /**
     * @author ruoxi
     * @createTime 2022/9/14 16:45
     */
    @Service
    @Log4j
    public class QuartzService {
    
        @Autowired
        private Scheduler scheduler;
    
        /**
         * 新增定时任务
         *
         * @param jName 任务名称
         * @param jGroup 任务组
         * @param tName 触发器名称
         * @param tGroup 触发器组
         * @param cron cron表达式
         */
        public void addjob(String jName, String jGroup, String tName, String tGroup, String cron) {
            try {
                // 构建JobDetail
                JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                        .withIdentity(jName, jGroup)
                        .build();
                // 按新的cronExpression表达式构建一个新的trigger
                CronTrigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(tName, tGroup)
                        .startNow()
                        .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                        .build();
                // 启动调度器
                scheduler.start();
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (Exception e) {
                System.out.println("创建定时任务失败" + e);
            }
        }
    
        public void pausejob(String jName, String jGroup) throws SchedulerException {
            scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
        }
    
        public void resumejob(String jName, String jGroup) throws SchedulerException {
            scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
        }
    
        public void rescheduleJob(String jName, String jGroup, String cron) throws SchedulerException {
            TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup);
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            // 按新的trigger重新设置job执行,重启触发器
            scheduler.rescheduleJob(triggerKey, trigger);
        }
    
        public void deletejob(String jName, String jGroup) throws SchedulerException {
            scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup));
            scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    具体的cron表达式可以看这篇博客 Cron表达式

  • 相关阅读:
    【RISC-V设计-10】- RISC-V处理器设计K0A之IDU
    Java中BIO、NIO、AIO详解
    Wireshark抓包:理解TCP三次握手和四次挥手过程
    优化实战篇—自关联的优化
    “传统”开发与AI开发的区别与联系(更新了GPT3.5的反馈)
    Navigation 组件(二) Fragment 跳转带参数,动画
    Map集合中value()与keySet()、entrySet()区别
    TSINGSEE青犀智能分析网关如何助力别墅区域监控智能化信息化发展?
    labelme做标注
    四平方和,激光炸弹
  • 原文地址:https://blog.csdn.net/qq_43564410/article/details/126841045