SpringBoot专栏:动态设置定时任务(Scheduling Tasks)与并发编程(15讲)

 

前情回顾

通过上篇文章:SpringBoot专栏:集成定时ScheduledTasks任务(第14讲)的简单讲解,我们都看到了SpringBoot做了很多自动配置,使集成变得异常简单。

然则我们不应该停留在基本使用上,所有的技术都是要支撑业务的,所以我们应该会想到如下问题(想到的同学加薪

1)定时任务会在什么业务场景上使用?

2)定时任务如果像上篇在代码中配置(java类),定时时间时间更改了怎么办,是否需要重启服务

3)定时会存在并发问题吗?

...

问题汇总(加薪):

定时任务设置会遇到页面化的操作,也就意味着我们需要动态的设置定时任务 停止、运行、重启;在执行定时任务的同时稍微考虑下并发遇到的场景;在运用定时任务定时任务fixedRatefixedDelay选择合适的场景使用。

 基于上面问题,特地编写了一个demo样例,帮我们解决动态设置定时任务,详解fixedRatefixedDelay,插入并发编程概念(主要希望大家了解下该技能点)

定时任务业务场景

1. 比如周期性更新数据库

2.定时跑一些评分、统计数据等

3.延迟执行..

不管什么场景,只要我们掌握了核心编程技术,所以都能get的思路。

好了样例开始,不明白的可以私信或者留言 @架构师速成记

案例讲解(springboot-09-tasks)

启动类(如上篇文章所述)

新增 ScheduledTasks

@Component
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");

    //可以用原子方式更新的 boolean 值
    private AtomicBoolean firstTime = new AtomicBoolean(true);

    //AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题。
    private AtomicInteger number = new AtomicInteger();

    /**
     *  @Scheduled(fixedRate = 2000) :上一次开始执行时间点之后5秒再执行
      * @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
     *  @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
     *  @Scheduled(cron=" /5 ") :通过cron表达式定义规则,什么是cro表达式,自行搜索引擎。
     *
     *  fixedRate 还有一个误区就是,以为任务时长超过 fixedRate 时会启动多个任务实例,
     *             其实不会; 只不过会在上次任务执行完后立即启动下一轮。除非这个 Job 方法用
     *             @Async 注解了,使得任务不在 TaskScheduler 线程池中执行,而是每次创建新线程来执行
     *             通过下面的这个列子我们可以看到在第一休眠7秒之后 ,以后的几个定时任务同时执行
     *  fixedDelay 的逻辑就相当简单:总是上次任务结束 5 秒后,fixedDelay 不存在任务的预先编排操作,是相机而为。
     */
    @Scheduled(fixedRate = 2000)
    public void reportCurrentTime() {
        LocalTime start = LocalTime.now();
        System.out.println(Thread.currentThread() + " 开始时间: " + number.incrementAndGet() + " -->"  + start);
//        以原子方式设置为给定值,并返回以前的值
        if (firstTime.getAndSet(false)) {
            try {
                //休眠7秒
                Thread.sleep(7000);
            } catch (InterruptedException e) {
            }
        }
        LocalTime end = LocalTime.now();
        System.out.println(Thread.currentThread() + " 运行结束: " + number.get() + " @ " + end
                + ", 任务用时 " + (ChronoUnit.SECONDS.between(start, end)));
    }

}

Controller类

@RestController
@Component  //不要忘记!不要忘记!
public class TaskController {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");

    private String cronStr = "*/5 * * * * *";

    /**
     * 首先这里我们需要重新认识一个类ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度。
     *
     * ThreadPoolTaskScheduler.schedule()方法会创建一个定时计划ScheduledFuture,在这个方法需要添加两个参数,
     * Runnable(线程接口类) 和CronTrigger(定时任务触发器)
     */
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    /**
     * ScheduledFuture中有一个cancel可以停止定时任务
     */
    private ScheduledFuture<?> future;

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        return new ThreadPoolTaskScheduler();
    }

    /**
     * 开始一个定时任务
     * @return
     */
    @RequestMapping("/start")
    public String startCron() {

        future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("0/5 * * * * *"));
        System.out.println("DynamicTask.startCron()");
        return "startCron";
    }

    /**
     * 关闭定时任务
     * @return
     */
    @RequestMapping("/stop")
    public String stopCron() {

        if (future != null) {
            future.cancel(true);
        }
        System.out.println("动态关闭定时任务");
        return "success";
    }

    /**
     * 重启定时任务
     * @return
     */
    @RequestMapping("/restart")
    public String restart() {
        stopCron();// 先停止,在开启.
        future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("*/10 * * * * *"));
        System.out.println("动态重启");
        return "动态改定时任务";
    }

    @GetMapping(value = "start1")
    public String startTask(){
            System.out.println("startCron1 >>>>");
            threadPoolTaskScheduler.schedule(new MyRunnable(), new Trigger(){
                @Override
                public Date nextExecutionTime(TriggerContext triggerContext){
                    System.out.println("开始运行"+new Date());
                    return new CronTrigger(cronStr).nextExecutionTime(triggerContext);
                }
            });

            System.out.println("startCron1 <<<<");
            return "success";
    }

    /**
     * run方法
     */
    private class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("run方法运行," + dateFormat.format(new Date()));
        }
    }
}

测试讲解

ScheduledTasks类中有个定时方法,启动后如下

分析:

1)fixedRatefixedDelay两者区别

通过下面的这个列子我们可以看到在第一休眠7秒之后 ,以后的几个定时任务同时执行

     fixedDelay 的逻辑就相当简单:总是上次任务结束 5 秒后,fixedDelay 不存在任务的预先编排操作,是相机而为。

以上也是fixedRatefixedDelay两者的区别。

2)并发语法

细心的同学可以看到我们用到了AtomicBoolean、AtomicInteger等

AtomicBoolean:可以用原子方式更新的 boolean 值

firstTime.getAndSet(false): 以原子方式设置为给定值,并返回以前的值

AtomicInteger:AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题

主要稍微带了并发编程遇到的基本语法,大家可以留意下

3)动态设置定时任务参考controller

启动定时任务

停止定时任务

日志如下:可以看到定时任务可以控制启动和停止(其它的重启以及其他的操作可以参考源码)

2018-12-29 21:09:21.311  INFO 30064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 38 ms
DynamicTask.startCron()
run方法运行,2018-12-29 21:09:25
run方法运行,2018-12-29 21:09:30
run方法运行,2018-12-29 21:09:35
run方法运行,2018-12-29 21:09:40
run方法运行,2018-12-29 21:09:45
动态关闭定时任务

End

定时任务到这就结束了,也希望大家对定时任务有个新的认识,不断的扩展新技能点。

快元旦了,领导请我们聚餐,每人都喝了几瓶,结果回来晕乎乎的,码了2个小时才码完这篇,希望对大家有帮助。

坚持写作1个月了,现在发现朋友的一次转发、关注是对本文的最大支持,鼓励下呗,转发支持一次

源码下载(今天刚码的)

https://github.com/shinians/springboot-demos

END2

对了还有个Cron 表达式的遗留问题,不会配置?没事,没关系,给提供下自动生成工具

更多信息可以咨询 @架构师速成记

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十年呵护

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值