Spring Schedule

简介

背景

在项目开发过程中,我们经常需要执行具有周期性的任务。通过定时任务可以很好的帮助我们实现。

我们拿常用的几种定时任务框架做一个比较:

定时任务框架Cron表达式固定间隔执行固定频率执行任务持久化开发难易度
JDK TimerTask不支持支持支持不支持一般
Spring Schedule支持支持支持不支持简单
Quartz支持支持支持支持困难

优点

基于注解来设置调度器。
非常方便实现简单的调度。
对代码不具有入侵性,非常轻量级。
所以我们会发现,spring schedule 用起来很简单,非常轻量级, 对代码无侵入性, 我们只需要注重业务的编写, 不需要关心如果构造Scheduler。

缺点

一旦调度任务被创建出来, 不能动态更改任务执行周期, 对于复杂的任务调度有一定的局限性。

使用说明

    @Component
    public class Demo{
    
        @Scheduled(fixedRate = 1000)
        public void do(){
            doSomething();
        }
    }
以上是1秒执行一次。

注解详解

spring schedule的核心就是Scheduled注解的使用
    public @interface Scheduled {
        String cron() default ""; // 使用cron表达式
        String zone() default "";
        long fixedDelay() default -1L; //每次执行任务之后间隔多久再次执行该任务。
        String fixedDelayString() default "";
        long fixedRate() default -1L; // 执行频率,每隔多少时间就启动任务,不管该任务是否启动完成
        String fixedRateString() default "";
        long initialDelay() default -1L;  //初次执行任务之前需要等待的时间
        String initialDelayString() default "";
    }

SpringBoot集成schedule

1、添加maven依赖包

由于Spring Schedule包含在spring-boot-starter基础模块中,所有不需要增加额外的依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、启动类,添加启动注解

在springboot入口或者配置类中增加@EnableScheduling注解即可启用定时任务。
    @EnableScheduling
    @SpringBootApplication
    public class ScheduleApplication {
        public static void main(String[] args) {
            SpringApplication.run(ScheduleApplication.class, args);
        }
    }

添加定时任务

我们将对Spring Schedule三种任务调度器分别举例说明。
1.3.1Cron表达式
类似于Linux下的Cron表达式时间定义规则。Cron表达式由6或7个空格分隔的时间字段组成,如下图:
位置时间域名允许值允许的特殊字符
10-59,-*/
2分钟0-59,-*/
3小时0-23,-*/
4日期1-31,-*/L W C
5月份1-12,-*/
6星期1-7,-*/L C #
7年(可选)空值 1970-2099,-*/
常用表达式:
表达式描述
0/30 * * * * *每30秒执行一次
0 0/5 * * * *每5分钟执行一次
0 0 0 * * *每天凌晨执行
0 0 8, 12, 17 * * *每天8点、12点、17点整执行
0 30 3-5 * * *每天3点~5点 30分时执行
举个栗子:
添加一个work()方法,每10秒执行一次。

注意:当方法的执行时间超过任务调度频率时,调度器会在下个周期执行。

如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第20秒。
    @Component
    public class MyTask {
        @Scheduled(cron = "0/10 * * * * *")
        public void work() {
            // task execution logic
        }
    }
1.3.2 固定间隔任务
下一次的任务执行时间,是从方法最后一次任务执行结束时间开始计算。并以此规则开始周期性的执行任务。

举个栗子:

添加一个work()方法,每隔10秒执行一次。

例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第22秒。
    @Scheduled(fixedDelay = 1000*10)
    public void work() {
        // task execution logic
    }
1.3.3 固定频率任务
按照指定频率执行任务,并以此规则开始周期性的执行调度。

举个栗子:

添加一个work()方法,每10秒执行一次。

注意:当方法的执行时间超过任务调度频率时,调度器会在当前方法执行完成后立即执行下次任务。

例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第12秒。
    @Scheduled(fixedRate = 1000*10)
    public void work() {
        // task execution logic
    }

配置TaskScheduler线程池

在实际项目中,我们一个系统可能会定义多个定时任务。那么多个定时任务之间是可以相互独立且可以并行执行的。

通过查看org.springframework.scheduling.config.ScheduledTaskRegistrar源代码,发现spring默认会创建一个单线程池。这样对于我们的多任务调度可能会是致命的,当多个任务并发(或需要在同一时间)执行时,任务调度器就会出现时间漂移,任务执行时间将不确定。
    protected void scheduleTasks() {
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        //省略...
    }

自定义线程池

新增一个配置类,实现SchedulingConfigurer接口。重写configureTasks方法,通过taskRegistrar设置自定义线程池。
    @Configuration
    public class ScheduleConfig implements SchedulingConfigurer {
        @Override
	    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
	        taskRegistrar.setTaskScheduler(taskScheduler());
	    }

	    @Bean()
	    public TaskScheduler taskScheduler() {
	    	/**
         	* 使用优先级队列DelayedWorkQueue,保证添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被获取。
         	* 资料https://www.jianshu.com/p/587901245c95
         	*/
	        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
	        // 配置核心线程池大小,根据任务数量定制,默认的线程池最大能创建的线程数目大小是Integer.MAX_VALUE
	        taskScheduler.setPoolSize(20);
	        // 线程名称前缀
	        taskScheduler.setThreadNamePrefix("xx-task-scheduler-thread-");
	        // 线程池关闭前最大等待时间,确保最后一定关闭
	        taskScheduler.setAwaitTerminationSeconds(30);
	        // 线程池关闭时等待所有任务完成
	        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
	        // 任务丢弃策略 ThreadPoolExecutor.AbortPolicy()丢弃任务并抛出RejectedExecutionException异常。
	        taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
	        return taskScheduler;
	    }
    }

使用SpringBoot的@Async来执行多线程并行处理

@Async配置
    /**
     * 线程配置
     * @author yc
     */
    
    @Configuration
    public class ThreadConfig implements AsyncConfigurer {
        /**
         * The {@link Executor} instance to be used when processing async
         * method invocations.
         * 默认策略为ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * 默认keepAliveTime时间为60秒
         * corePoolSize默认为1
         * maxPoolSize 默认为 Integer.MAX_VALUE;
         * keepAliveSeconds 默认为 60秒(单位为TimeUnit.SECONDS)
         * queueCapacity 默认为 Integer.MAX_VALUE;
         * 当队列参数为整数为LinkedBlockingQueue
         * 其他队列参数为SynchronousQueue
         *
         */
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(20);
            executor.setMaxPoolSize(40);
            executor.setQueueCapacity(100);
            executor.initialize();
            return executor;
        }
        /**
         * The {@link AsyncUncaughtExceptionHandler} instance to be used
         * when an exception is thrown during an asynchronous method execution
         * with {@code void} return type.
         */
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return null;
        }
    }
定时任务使用
    @Component
    @Slf4j
    @Async
    public class ScheduleTask {
    
        @Autowired
        private XXXTask xxxTask;
    
        /**
         * xxx定时任务
         */
        @Scheduled(cron = "0 */2 * * * ?")
        public void xxxTask(){
            log.info("定时任务xxxTask开始时间:"+ DateUtils.currentTime());
            try{
                xxxTask.xxx();
            }catch (WalletException e){
                log.error("--------------------------------发生时间{},异常是{}",
                        DateUtils.currentTime(),e.getResultVoError().getMsg());
            }
            log.info("定时任务xxxTask结束时间:"+ DateUtils.currentTime());
        }
    }

参考网站

https://www.cnblogs.com/skychenjiajun/p/9057379.html?utm_source=tuicool&utm_medium=referral
https://www.jianshu.com/p/587901245c95
https://www.cnblogs.com/dolphin0520/p/3932921.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值