Spring關(guān)于@Scheduled限制的問題
Spring @Scheduled限制
@Scheduled具有一定的限制性,它畢竟不是quartz,只是簡單的定時,比jdk Timer就加入了線程池而以
@Scheduled不支持年份定時@Scheduled不支持W L這些字母
沒辦法 如果非要使用那就只能放棄注解使用XML方式了
Spring多定時任務(wù)@Scheduled執(zhí)行阻塞
一. 問題描述
最近項(xiàng)目中發(fā)現(xiàn)一個問題,計(jì)劃每日凌晨4:40執(zhí)行一個定時任務(wù),使用注解方式: @Scheduled(cron = “0 40 4 * * ?”),cron表達(dá)式明顯沒有問題,但是這個定時任務(wù)總是不按時執(zhí)行,有時候得等到8點(diǎn)多,有時候9點(diǎn)多才執(zhí)行。后來查了下,原來這種定時方式默認(rèn)是單線程執(zhí)行的,恰好我這里有多個定時任務(wù),并且其中有個在4:40之前的定時任務(wù)比較耗時,導(dǎo)致4:40的任務(wù)只能等待之前的任務(wù)執(zhí)行完成才能夠觸發(fā),所以要自己手動把定時任務(wù)設(shè)置成多線程的方式才行。
二. 場景復(fù)現(xiàn)
項(xiàng)目描述:使用Springboot進(jìn)行開發(fā)
設(shè)置兩個定時任務(wù),每5s執(zhí)行一次,并打印出其執(zhí)行情況
代碼如下:
@Component
@Log4j2
public class ScheduledTask {
@Scheduled(cron = "0/5 * * * * ?")
public void task1() throws InterruptedException {
log.info("I am task11111111, current thread: {}", Thread.currentThread());
while (true) {
//模擬耗時任務(wù),阻塞10s
Thread.sleep(10000);
break;
}
}
@Scheduled(cron = "0/5 * * * * ?")
public void task2() {
log.info("I am task22222222, current thread: {}", Thread.currentThread());
}
}
執(zhí)行結(jié)果如下:
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]
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]
由結(jié)果可見,task1與task2由同一個線程Thread[scheduling-1,5,main]執(zhí)行,也即該定時任務(wù)默認(rèn)使用單線程,并且由于task1阻塞了10s,導(dǎo)致本應(yīng)5s執(zhí)行一次的定時任務(wù)10s才執(zhí)行一次。
三. 解決方案
網(wǎng)上有多種解決方案,以下列舉兩種
方案一:使用@Async注解實(shí)現(xiàn)異步任務(wù)
這種方式比較簡單,在定時任務(wù)上加上@Async注解,注意:需啟動類配合加上 @EnableAsync才會生效
代碼如下:
@Component
@Log4j2
public class ScheduledTask {
@Async
@Scheduled(cron = "0/5 * * * * ?")
public void task1() throws InterruptedException {
log.info("I am task11111111, current thread: {}", Thread.currentThread());
while (true) {
//模擬耗時任務(wù),阻塞10s
Thread.sleep(10000);
break;
}
}
@Async
@Scheduled(cron = "0/5 * * * * ?")
public void task2() {
log.info("I am task22222222, current thread: {}", Thread.currentThread());
}
}
運(yùn)行結(jié)果:
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]
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]
由運(yùn)行日志可見,定時每5s執(zhí)行一次已生效,且每次任務(wù)使用的線程不一樣,也即實(shí)現(xiàn)了多線程執(zhí)行定時任務(wù),不會出現(xiàn)任務(wù)等待現(xiàn)象。此方式據(jù)說默認(rèn)線程池大小為100,要是任務(wù)不多的話有點(diǎn)大材小用了,所以我覺得第二種方式比較好。
方案二:手動設(shè)置定時任務(wù)的線程池大小
定時任務(wù)代碼部分還原,不使用@Async注解,新增啟動代碼配置:
@Configuration
public class AppConfig implements SchedulingConfigurer {
@Bean
public Executor taskExecutor() {
//指定定時任務(wù)線程數(shù)量,可根據(jù)需求自行調(diào)節(jié)
return Executors.newScheduledThreadPool(3);
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(taskExecutor());
}
}
運(yùn)行結(jié)果如下:
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]
由結(jié)果可見,第二種方式也實(shí)現(xiàn)了多線程任務(wù)調(diào)度。
四. 總結(jié)
兩種方式各有優(yōu)缺點(diǎn):
| 比較 | 方案一 | 方案二 |
|---|---|---|
| 優(yōu)點(diǎn) | 注解方式使用簡單,代碼量少 | 配置靈活,線程數(shù)可控 |
| 缺點(diǎn) | 線程數(shù)不可控,可能存在資源浪費(fèi) | 需要增加編碼 |
留個坑,從日志上看@Async方式針對同一任務(wù)也是異步的,也即task1每5s會執(zhí)行一次,但是方式二貌似對同一個任務(wù)不會生效,task1執(zhí)行的時候需等待上一次執(zhí)行結(jié)束才會觸發(fā),并沒有每5s執(zhí)行一次。關(guān)于這個現(xiàn)象,下次再琢磨…
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java模擬計(jì)算機(jī)的整數(shù)乘積計(jì)算功能示例
這篇文章主要介紹了Java模擬計(jì)算機(jī)的整數(shù)乘積計(jì)算功能,簡單分析了計(jì)算機(jī)數(shù)值進(jìn)制轉(zhuǎn)換與通過位移進(jìn)行乘積計(jì)算的原理,并結(jié)合具體實(shí)例給出了java模擬計(jì)算機(jī)成績運(yùn)算的相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
spring?cloud之eureka高可用集群和服務(wù)分區(qū)解析
這篇文章主要介紹了spring?cloud之eureka高可用集群和服務(wù)分區(qū)解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java線程間協(xié)作wait、notify和notifyAll詳解
這篇文章主要介紹了Java線程間協(xié)作wait、notify和notifyAll詳解,在 Java 中可以用 wait、notify 和 notifyAll 來實(shí)現(xiàn)線程間的通信,盡管關(guān)于wait和notify的概念很基礎(chǔ),它們也都是Object類的函數(shù),但用它們來寫代碼卻并不簡單,,需要的朋友可以參考下2023-10-10
Java 大小寫最快轉(zhuǎn)換方式實(shí)例代碼
這篇文章主要介紹了Java 大小寫最快轉(zhuǎn)換方式實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-07-07
Spring中BeanUtils.copyProperties的坑及解決
這篇文章主要介紹了Spring中BeanUtils.copyProperties的坑及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09

