Quartz定时任务基础

springBoot有一个定时执行某个方法的 注解:

@Scheduled

可以满足挺多的需求,但是到了一些场景,就显得比较麻烦,比如:

机器待机五分钟后执行切换待机状态。如果是按照使用@Scheduled注解,就得持久化一个表,里面存放机器信息,转换时间,然后@Scheduled一个方法,每隔一定时间扫描全表,查询需要转换的机器。

还有一种有趣的解决办法:

它会定义一个小顶堆,存放需要执行的任务(主要还是存放的该任务的执行时间),

小顶堆结构;

Quartz定时任务基础_第1张图片

添加任务只需要在最后一个位置加上,然后上浮操作(和父级对比,父级更大就交换,父级小就不动,一直对比,直到父级小不动),所以会发现,每次都是操作父子级,如何快速找到父级,成了最大问题,于是,存储结构选择了数组:

只需要将子集下标/2=取整,就是父级下标。

存入:

Quartz定时任务基础_第2张图片

也就是说,每次只需要判断顶部数据是否可以执行即可,比上面那种直接扫描全表性能更快。

执行一个任务后,就会刷新顶堆,拿出最后一个。

取出:

Quartz定时任务基础_第3张图片

package com.quxiao;

import java.time.LocalTime;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 11:00
 **/
public class t1 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        for (int i = 0; i < 2; i++) {
            TimerTask timerTask = new TimerTest1("" + i);
            timer.schedule(timerTask, new Date(), 2000);
        }
    }

    static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(60));

    static class TimerTest1 extends TimerTask {
        String name;

        public TimerTest1() {

        }

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

        @Override
        public void run() {
//如果不使用线程池运行,会导致延迟执行,因为run方法是阻塞的,无法做到异步
            poolExecutor.execute(() -> {
                System.out.println(name + ": " + (LocalTime.now().toString()));
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
}

Timer是java自带的一个定时任务,定时执行指定的方法,

还有一种就是时间轮,Cron表达式,每个位置指定日期,这里我们使用quartz框架来进行调度器:

        
            org.springframework.boot
            spring-boot-starter-quartz
        

直接使用和springBoot项目运行

首先定义执行什么,怎么执行,以及储存任务

执行什么:

package com.quartz.jop;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.time.LocalTime;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:00
 **/
public class TestJop implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println(LocalTime.now().toString());
    }
}

只有实现了job接口的类才能执行,我们可以在里面执行任何东西。

然后就是怎么执行:

package com.quartz.controller;

import com.quartz.jop.TestJop;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:04
 **/
@RestController
public class t1 {
    @RequestMapping("test1")
    public void test1() {
        //定义需要执行的方法(就是实现类)
        JobDetail jobDetail = JobBuilder.newJob(TestJop.class)
                .withIdentity("job1", "group1")
                .build();
        //如何执行,多少秒一次,什么时候执行,都是在这里定义
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "trigger1")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
                .build();
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //储存起来
            scheduler.scheduleJob(jobDetail, trigger);
            //启动    
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        } finally {
        }

    }
}

在定义定时器时,可以将需要的值传入:

package com.quartz.controller;

import com.quartz.jop.TestJop;
import com.quartz.jop.t2;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:04
 **/
@RestController
public class t1 {
    @RequestMapping("test1")
    public void test1() {
        JobDetail jobDetail = JobBuilder.newJob(TestJop.class)
                .withIdentity("job1", "group1")
                .usingJobData("name","你爹")
                .build();

        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "trigger1")
                .usingJobData("name","ty")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(2))
                .build();
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        } finally {
        }

    }
}

其实就是一个map

取出:

package com.quartz.jop;

import org.quartz.*;

import java.time.LocalTime;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:00
 **/
public class TestJop implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        Trigger trigger = context.getTrigger();
        JobDataMap map = trigger.getJobDataMap();
        System.out.println(map.get("name"));

        System.out.println(LocalTime.now().toString());

        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        System.out.println(jobDataMap.get("name"));

    }
}

还可以直接把trigger 和jobDetail取出来:

JobDataMap mergedJobDataMap = context.getMergedJobDataMap();

不过很显然,可是不能重复的,map嘛

还有一点就时,调度器默认是并行的:

package com.quartz.jop;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:00
 **/
public class TestJop implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println(1123211);
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(3));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
//        JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
//        System.out.println(mergedJobDataMap.get("name"));

    }
}

 也就是说,不管上一个任务是否执行完毕,我到了点,本次任务必然执行-。

使用@DisallowConcurrentExecution注解可以变为串行,也就是同步执行

@PersistJobDataAfterExecution可以让jobDetail的jobDataMap一直保存

package com.quartz.jop;

import org.quartz.*;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-18 20:00
 **/
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class TestJop implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        jobDataMap.put("name", jobDataMap.get("name") + "123");
        System.out.println(jobDataMap.get("name"));
    }
}





cron 模式定时器

前面用的是基础的执行多少次,指定时间执行还是得用cron方式,注意springBoot有自带的cron执行

@Scheduled

注解,但是它只能标识在方法上,也就是说只能某个方法,并不能达到前提条件,例如:

设备闲置5分钟,改为待机状态,只要在五分钟内运行,我们就把这个定时任务删掉就好。

package com.quartz.util;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @program: springBoot
 * @author: quxiao
 * @create: 2023-11-26 10:56
 **/
public class DateUtil {
    /**
     * @param date 指定日期
     * @return 返回指定日期的cron表达式
     */
    public synchronized static String getCron(Date date) {
        String dateFormat = "ss mm HH dd MM ? yyyy";

        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        String formatTimeStr = null;
        if (date != null) {
            formatTimeStr = sdf.format(date);
        }
        return formatTimeStr;
    }

    /**
     * @param date 指定日期
     * @param unit 时间单位
     * @param number 增加多少
     * 增加指定时间
     */
    public synchronized static Date addTime(Date date, TemporalUnit unit,long number){
        LocalDateTime date1 = date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        LocalDateTime nextWeek = date1.plus(number, unit);
        return Date.from(nextWeek.atZone(ZoneId.systemDefault()).toInstant());
    }
}

 先给一个时间转换的工具类

使用cron方式只需要将触发器改了就行: (接上序的基础springboot代码)

  @Autowired
    Scheduler scheduler;

    @PostMapping("test1")
    public void test1() {
        JobDetail jobDetail = JobBuilder.newJob(TestJop.class)
                .withIdentity("job1", "group1")
                .build();

        Date date = DateUtil.addTime(new Date(), ChronoUnit.SECONDS, 5);
        //cron表达式
        String cron = DateUtil.getCron(date);
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "trigger1")
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))//达到指定时间执行
                .build();
        try {
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

 Quartz定时任务基础_第4张图片

 我们之所以使用这个框架,就是因为他可以支持删除、暂停、定时任务,所以就能够把定时任务存起来持久化管理。

删除、暂停、恢复定时任务

 @PostMapping("test2")
    public void t2() {
        try {
            JobKey key = new JobKey("job1","group1");
            scheduler.deleteJob(key);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

在这里我们前面定义的group分组就起到作用了,通过指定定时任务名job金额分组名group。

所以剩余两个操作也是一样:

  @PostMapping("test3")
    public void t3() {
        try {
            JobKey key = new JobKey("job1", "group1");
            scheduler.pauseJob(key);
            for (int i = 0; i < 10; i++) {
                System.out.println("暂停中");
                TimeUnit.SECONDS.sleep(1);
            }
            scheduler.resumeJob(key);
        } catch (Exception e) {
        }
    }

你可能感兴趣的:(项目小技巧,java,开发语言)