Springboot-主线5-定时任务

定时任务

引言

现代的应用程序已经从最简单的crud操作改变为程序自动定时定点的调度与执行。

很多业务需求已经离不开定时任务的处理,如每天汇总日志,每月的移动数据,不可能人为的去处理这些,这个时候定时任务能够很好的解决这个问题。

java开发的定时任务的方式主要有以下几种:

  1. timer
  2. ScheduledExecutorService
  3. Spring Task
  4. Quartz

所以接下来依次进行介绍!

timer

KnowPoint

​ timer是基于jdk中java.util.Timer的类。

具体的调度方法:

  1. 在特定的时间之后执行任务,并且只执行一次。
public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }
  1. 在指定的时间执行任务,只执行一次
public void schedule(TimerTask task, Date time) {
        sched(task, time.getTime(), 0);
    }
  1. 在特定延迟之后第一次执行,然后按照间隔时间,重复执行。
public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }
  1. 执行第一次执行后,然后按照间隔时间,重复执行。
public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }
  1. 执行第一次执行,然后按照间隔时间,重复执行。
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, period);
    }
  1. 第一次执行执行之后,特定频率执行
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), period);
    }
private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        // Constrain value of period sufficiently to prevent numeric
        // overflow while still being effectively infinitely large.
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }

            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

相信各位看到这里对于schedule以及scheduleAtFixedRate有一点疑惑,感觉方法功能有点相似。但是还是有一点的区别:

假如指定开始执行的时间在当前系统时间之前呢,scheduleAtFixedRate会把已经过去的时间作为周期执行,而schedule并不会。

实例:

一秒执行一次

 @GetMapping("Task/Timer")
    public String doTimer(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {

                log.info("timer定时任务启动"+new Date()+"线程名称:"+Thread.currentThread().getName());
            }
        },1000,1000);
        return "timer";
    }

结果:

image-20201124102051424

ScheduledExecutorService

knowPoint

由于Timer不支持多线程,所以ScheduledExecutorService的出现可以说把Timer替代了。

直接上例子

实例

@GetMapping("/Task/sch")
    public String Scheula(){
        // 设置最大的线程数
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
        // 设置选择定时的服务
        service.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                log.info("任务1:"+"ScheduledExecutorService定时任务执行:" + new Date()+"当前线程名称"+Thread.currentThread().getName());
            }
        }, 500, 500, TimeUnit.MILLISECONDS);//首次延迟0.5秒,之后每0.5秒执行一次
        service.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                log.info("任务2:"+"ScheduledExecutorService定时任务执行:" + new Date()+"当前线程名称"+Thread.currentThread().getName());
            }
        }, 500, 500, TimeUnit.MILLISECONDS);//首次延迟0.5秒,之后每0.5秒执行一次
        log.info("初始:"+"ScheduledExecutorService定时任务启动:" + new Date()+"当前线程名称"+Thread.currentThread().getName());
        return "ScheduledExecutorService!";
    }

结果:

image-20201124105348539

SpingTask

knowPoint

使用@Scheduled注解即可轻松搞定

实例

启动springboot自带的定时任务,就需要我们开启这个功能,

启动类加上了@EnableScheduling来开启定时任务功能

之后编写一个启动类,系统启动后会自动扫描,自动执行。

@Component
public class demo {

    @Scheduled(fixedRate=500)
    public void getCurrentDate() {
        log.info("Scheduled定时任务执行:" + new Date()+"线程名:"+Thread.currentThread().getName());
    }
}

结果:

image-20201124150617344

这个功能的实现,重点在于@scheduled注解的使用,

点进源码我们可以看到

image-20201124151038445

一共八种类型的参数选择

  1. cron:顾名思义:接受的为cron表达式
  2. zone:时区,接收一个java.util.TimeZone#IDcron表达式会基于该时区解析。默认是一个空字符串,即取服务器所在地的时区。比如我们一般使用的时区Asia/Shanghai。该字段我们一般留空。
  3. fixedDelay:上一次执行完毕时间点后多长时间再执行
  4. fixedDelayString:字符串形式,支持占位
  5. fixedRate:上一次开始执行时间点之后多长时间再执行
  6. fixedRateString:字符串形式,支持占位
  7. initialDelay:第一次延迟多长时间后再执行
  8. initialDelayString:字符串形式,支持占位

从上面的结果我们可以看出,多任务下,却只有一个线程。

为了可以使用多线程进行任务执行,进行进一步的完善

编写配置类:

@Configuration
@EnableAsync
public class Config {
    /**
     * 配置线程池
     * @return
     */
    @Bean(name = "TaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-Scheduled-");
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }
}

并且方法上加上:

image-20201124153629849

动态添加定时任务

使用注解的方式,无法实现动态的修改或者添加新的定时任务的,这个使用就需要使用编程的方式进行任务的更新操作了。可直接使用ThreadPoolTaskScheduler或者SchedulingConfigurer接口进行自定义定时任务创建。

创建一个ThreadPoolTaskScheduler类

@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
    executor.setPoolSize(20);
    executor.setThreadNamePrefix("oKong-taskExecutor-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    //调度器shutdown被调用时等待当前被调度的任务完成
    executor.setWaitForTasksToCompleteOnShutdown(true);
    //等待时长
    executor.setAwaitTerminationSeconds(60);
    return executor;
}

编写控制类:

@Autowired
TaskScheduler taskScheduler;

@GetMapping("/poolTask")
public String threadPoolTaskScheduler() {

    taskScheduler.schedule(new Runnable() {
        
        @Override
        public void run() {
            log.info("ThreadPoolTaskScheduler定时任务:" + new Date());
        }
    }, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
    return "ThreadPoolTaskScheduler!";
}

访问接口-成功

也可以使用:SchedulingConfigurer

编写配置类:

@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(taskExecutor());
        taskRegistrar.getScheduler().schedule(new Runnable() {
            
            @Override
            public void run() {
                log.info("SchedulingConfigurer定时任务:" + new Date());
            }
        }, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
    }
    
    @Bean("taskExecutor")
    public TaskScheduler taskExecutor() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(20);
        executor.setThreadNamePrefix("oKong-Executor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

}

Quartz

knowPoint

Quartz有四个核心概念:

  • Job:是一个接口,只定义一个方法 execute(JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job(任务),JobExecutionContext 类提供了调度应用的一些信息;Job 运行时的信息保存在 JobDataMap 实例中。
  • JobDetail:Quartz 每次调度 Job 时,都重新创建一个 Job 实例,因此它不接受一个 Job 的实例,相反它接收一个 Job 实现类(JobDetail,描述 Job 的实现类及其他相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance() 的反射机制实例化 Job。
  • Trigger:是一个类,描述触发 Job 执行的时间触发规则,主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而 CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 15:00 ~ 16:00 执行调度等
  • Scheduler:调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一,JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail

总之:

​ Job 为作业的接口,为任务调度的对象;JobDetail 用来描述 Job 的实现类及其他相关的静态信息;Trigger 做为作业的定时管理工具,一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个触发器;Scheduler 做为定时任务容器,是 Quartz 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 Scheduler 都存有 JobDetail 和 Trigger 的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。

实例

第一步:引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

第二步:构建JobDetail:

@Configuration
public class SampleScheduler {
    @Bean
    public JobDetail sampleJobDetail() {
        // 链式编程,可以携带多个参数,在Job类中声明属性 + setter方法
        return JobBuilder.newJob(JobDemo.class).withIdentity("sampleJob")
                .usingJobData("name","World").storeDurably().build();
    }

    @Bean
    public Trigger sampleJobTrigger(){
        // 每隔两秒执行一次
        SimpleScheduleBuilder scheduleBuilder =
                SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
        return TriggerBuilder.newTrigger().forJob(sampleJobDetail()).withIdentity("sampleTrigger")
                .withSchedule(scheduleBuilder).build();
    }
}

第三步:书写类继承job接口

public class JonFrist implements Job {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

        System.out.println("CRON ----> schedule job1 is running ... + " + name + "  ---->  " + dateFormat.format(new Date()));
    }
}

另外一个类似。

构建schedule来执行任务

package com.task.timertask.Quartz;

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

@Component
public class CronSchedulerJob {
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    private void scheduleJob1(Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(JonFrist.class) .withIdentity("job1", "group1").build();
        // 6的倍数秒执行 也就是 6 12 18 24 30 36 42 ....
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/6 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
                .usingJobData("name","测试1").withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }

    private void scheduleJob2(Scheduler scheduler) throws SchedulerException{
        JobDetail jobDetail = JobBuilder.newJob(JobSecond.class) .withIdentity("job2", "group2").build();
        // 12秒的倍数执行  12  24 36  48  60
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/12 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group2")
                .usingJobData("name","测试2").withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }

    /**
     * @Author Smith
     * @Description 同时启动两个定时任务
     * @Date 16:31 2019/1/24
     * @Param
     * @return void
     **/
    public void scheduleJobs() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        scheduleJob1(scheduler);
        scheduleJob2(scheduler);
    }

}

启动方式:

  1. 项目启动时运行
@Component
public class MyStartupRunner implements CommandLineRunner {

    @Autowired
    public CronSchedulerJob scheduleJobs;

    @Override
    public void run(String... args) throws Exception {
        scheduleJobs.scheduleJobs();
        System.out.println(">>>>>>>>>>>>>>>定时任务开始执行<<<<<<<<<<<<<");
    }
}

CommandLineRunner接口可实现。

  1. 定时执行
@Configuration
@EnableScheduling
@Component
public class SchedulerListener {

    @Autowired
    public CronSchedulerJob scheduleJobs;

    @Scheduled(cron="0 47 16 24 1 ?")
    public void schedule() throws SchedulerException {
        scheduleJobs.scheduleJobs();
        System.out.println(">>>>>>>>>>>>>>>定时任务开始执行<<<<<<<<<<<<<");
    }

}

引出问题:

如果在分布式环境下应该如何解决呢?,请看下篇详解

致谢:

基于springboot的定时任务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值