Spring关于@Scheduled限制的问题
目录
- Spring @Scheduled限制
- Spring多定时任务@Scheduled执行阻塞
- 一. 问题描述
- 二. 场景复现
- 三. 解决方案
- 四. 总结
Spring @Scheduled限制 @Scheduled具有一定的限制性,它毕竟不是quartz,只是简单的定时,比jdk Timer就加入了线程池而以
@Scheduled
不支持年份定时@Scheduled
不支持W L这些字母
Spring多定时任务@Scheduled执行阻塞 【Spring关于@Scheduled限制的问题】
一. 问题描述
最近项目中发现一个问题,计划每日凌晨4:40执行一个定时任务,使用注解方式: @Scheduled(cron = “0 40 4 * * ?”),cron表达式明显没有问题,但是这个定时任务总是不按时执行,有时候得等到8点多,有时候9点多才执行。后来查了下,原来这种定时方式默认是单线程执行的,恰好我这里有多个定时任务,并且其中有个在4:40之前的定时任务比较耗时,导致4:40的任务只能等待之前的任务执行完成才能够触发,所以要自己手动把定时任务设置成多线程的方式才行。
二. 场景复现
项目描述:使用Springboot进行开发
设置两个定时任务,每5s执行一次,并打印出其执行情况
代码如下:
@Component@Log4j2public class ScheduledTask {@Scheduled(cron = "0/5 * * * * ?")public void task1() throws InterruptedException {log.info("I am task11111111, current thread: {}", Thread.currentThread()); while (true) {//模拟耗时任务,阻塞10sThread.sleep(10000); break; }}@Scheduled(cron = "0/5 * * * * ?")public void task2() {log.info("I am task22222222, current thread: {}", Thread.currentThread()); }}
执行结果如下:
2019-04-24 17:11:15.008 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[scheduling-1,5,main]由结果可见,task1与task2由同一个线程Thread[scheduling-1,5,main]执行,也即该定时任务默认使用单线程,并且由于task1阻塞了10s,导致本应5s执行一次的定时任务10s才执行一次。
2019-04-24 17:11:15.009 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:25.009 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:30.002 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:30.003 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:40.004 INFO 16868 --- [ scheduling-1] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[scheduling-1,5,main]
三. 解决方案
网上有多种解决方案,以下列举两种
方案一:使用@Async注解实现异步任务
这种方式比较简单,在定时任务上加上@Async注解,注意:需启动类配合加上 @EnableAsync才会生效
代码如下:
@Component@Log4j2public class ScheduledTask {@Async@Scheduled(cron = "0/5 * * * * ?")public void task1() throws InterruptedException {log.info("I am task11111111, current thread: {}", Thread.currentThread()); while (true) {//模拟耗时任务,阻塞10sThread.sleep(10000); break; }}@Async@Scheduled(cron = "0/5 * * * * ?")public void task2() {log.info("I am task22222222, current thread: {}", Thread.currentThread()); }}
运行结果:
2019-04-24 17:03:00.024 INFO 2152 --- [ task-1] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[task-1,5,main]由运行日志可见,定时每5s执行一次已生效,且每次任务使用的线程不一样,也即实现了多线程执行定时任务,不会出现任务等待现象。此方式据说默认线程池大小为100,要是任务不多的话有点大材小用了,所以我觉得第二种方式比较好。
2019-04-24 17:03:00.024 INFO 2152 --- [ task-2] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[task-2,5,main]
2019-04-24 17:03:05.001 INFO 2152 --- [ task-3] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[task-3,5,main]
2019-04-24 17:03:05.001 INFO 2152 --- [ task-4] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[task-4,5,main]
2019-04-24 17:03:10.002 INFO 2152 --- [ task-5] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[task-5,5,main]
2019-04-24 17:03:10.003 INFO 2152 --- [ task-6] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[task-6,5,main]
方案二:手动设置定时任务的线程池大小
定时任务代码部分还原,不使用@Async注解,新增启动代码配置:
@Configurationpublic class AppConfig implements SchedulingConfigurer {@Beanpublic Executor taskExecutor() {//指定定时任务线程数量,可根据需求自行调节return Executors.newScheduledThreadPool(3); }@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {scheduledTaskRegistrar.setScheduler(taskExecutor()); }}
运行结果如下:
2019-04-24 17:26:15.008 INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]由结果可见,第二种方式也实现了多线程任务调度。
2019-04-24 17:26:15.008 INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
2019-04-24 17:26:20.002 INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
2019-04-24 17:26:25.001 INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
2019-04-24 17:26:30.001 INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
2019-04-24 17:26:30.001 INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]
2019-04-24 17:26:35.001 INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]
四. 总结
两种方式各有优缺点:
比较 | 方案一 | 方案二 |
---|---|---|
优点 | 注解方式使用简单,代码量少 | 配置灵活,线程数可控 |
缺点 | 线程数不可控,可能存在资源浪费 | 需要增加编码 |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- Activiti(一)SpringBoot2集成Activiti6
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- 四首关于旅行记忆的外文歌曲
- 醒不来的梦
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- 关于自我为中心的一点感想
- 「按键精灵安卓版」关于全分辨率脚本的一些理解(非游戏app)
- 2018-07-09|2018-07-09 Spring 的DBCP,c3p0
- 关于Ruby的杂想