今天就跟大家聊聊有關(guān)SpringBoot 中@Schedule的原理是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供撫寧網(wǎng)站建設(shè)、撫寧做網(wǎng)站、撫寧網(wǎng)站設(shè)計(jì)、撫寧網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、撫寧企業(yè)網(wǎng)站模板建站服務(wù),10余年撫寧做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
我相信@Schedule默認(rèn)線程池大小的問題肯定是被很多拿來就用的朋友忽略的問題,默認(rèn)情況下@Schedule使用線程池的大小為1。
一般情況下沒有什么問題,但是如果有多個(gè)定時(shí)任務(wù),每個(gè)定時(shí)任務(wù)執(zhí)行時(shí)間可能不短的情況下,那么有的定時(shí)任務(wù)可能一直沒有機(jī)會(huì)執(zhí)行。
有興趣的朋友,可以試一下:
@Component public class BrigeTask { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Scheduled(cron = "*/5 * * * * ?") private void cron() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-cron:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } @Scheduled(fixedDelay = 5000) private void fixedDelay() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-fixedDelay:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } @Scheduled(fixedRate = 5000) private void fixedRate() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-fixedRate:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } }
上面的任務(wù)中,fixedDelay與cron,可能很久都不會(huì)被執(zhí)行。
要解決上面的問題,可以把執(zhí)行任務(wù)的線程池設(shè)置大一點(diǎn),怎樣設(shè)置通過實(shí)現(xiàn)SchedulingConfigurer接口,在configureTasks方法中配置,這種方式參見后面的代碼,這里可以直接注入一個(gè)TaskScheduler來解決問題。
@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(5); return taskScheduler; }
當(dāng)然也可以使用ScheduledExecutorService:
@Bean public ScheduledExecutorService scheduledExecutorService() { return Executors.newScheduledThreadPool(10); }
為啥這樣有效,請(qǐng)參考后面@Schedule原理。
@Schedule的三種方式cron、fixedDelay、fixedRate不管線程夠不夠都會(huì)阻塞到上一次執(zhí)行完成,才會(huì)執(zhí)行下一次。
如果任務(wù)方法執(zhí)行時(shí)間非常短,上面三種方式其實(shí)基本沒有太多的區(qū)別。
如果,任務(wù)方法執(zhí)行時(shí)間比較長(zhǎng),大于了設(shè)置的執(zhí)行周期,那么就有很大的區(qū)別。例如,假設(shè)執(zhí)行任務(wù)的線程足夠,執(zhí)行周期是5s,任務(wù)方法會(huì)執(zhí)行6s。
cron的執(zhí)行方式是,任務(wù)方法執(zhí)行完,遇到下一次匹配的時(shí)間再次執(zhí)行,基本就會(huì)10s執(zhí)行一次,因?yàn)閳?zhí)行任務(wù)方法的時(shí)間區(qū)間會(huì)錯(cuò)過一次匹配。
fixedDelay的執(zhí)行方式是,方法執(zhí)行了6s,然后會(huì)再等5s再執(zhí)行下一次,在上面的條件下,基本就是每11s執(zhí)行一次。
fixedRate的執(zhí)行方式就變成了每隔6s執(zhí)行一次,因?yàn)榘垂潭▍^(qū)間執(zhí)行它沒5s就應(yīng)該執(zhí)行一次,但是任務(wù)方法執(zhí)行了6s,沒辦法,只好6s執(zhí)行一次。
上面的結(jié)論都可以通過,最上面的示例驗(yàn)證,有興趣的朋友可以調(diào)整一下休眠時(shí)間測(cè)試一下。
在SpringBoot中,我們使用@EnableScheduling來啟用@Schedule。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }
EnableScheduling注解沒什么特殊,需要注意import了SchedulingConfiguration。
SchedulingConfiguration一看名字就知道是一個(gè)配置類,肯定是為了添加相應(yīng)的依賴類。
@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class SchedulingConfiguration { @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() { return new ScheduledAnnotationBeanPostProcessor(); } }
我們可以看到在SchedulingConfiguration創(chuàng)建了一個(gè)ScheduledAnnotationBeanPostProcessor。
看樣子SpringBoot定時(shí)任務(wù)的核心就是ScheduledAnnotationBeanPostProcessor類了,下面我們來看一看ScheduledAnnotationBeanPostProcessor類。
public class ScheduledAnnotationBeanPostProcessor implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware, SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean { }
ScheduledAnnotationBeanPostProcessor實(shí)現(xiàn)了很多接口,這里重點(diǎn)關(guān)注2個(gè),ApplicationListener和DestructionAwareBeanPostProcessor。
DestructionAwareBeanPostProcessor繼承了BeanPostProcessor。
BeanPostProcessor相信大家已經(jīng)非常熟悉了,就是在Bean創(chuàng)建執(zhí)行setter之后,在自定義的afterPropertiesSet和init-method前后提供攔截點(diǎn),大致執(zhí)行的先后順序是:
Bean實(shí)例化 -> setter -> BeanPostProcessor#postProcessBeforeInitialization ->
-> InitializingBean#afterPropertiesSet -> init-method -> BeanPostProcessor#postProcessAfterInitialization
我們看一下ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法:
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler || bean instanceof ScheduledExecutorService) { // Ignore AOP infrastructure such as scoped proxies. return bean; } Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) { Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> { Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( method, Scheduled.class, Schedules.class); return (!scheduledMethods.isEmpty() ? scheduledMethods : null); }); if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetClass); if (logger.isTraceEnabled()) { logger.trace("No @Scheduled annotations found on bean class: " + targetClass); } } else { // Non-empty set of methods annotatedMethods.forEach((method, scheduledMethods) -> scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean))); if (logger.isTraceEnabled()) { logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); } } } return bean; }
簡(jiǎn)單說一下流程:
找到所有的Schedule方法,把它封裝為ScheduledMethodRunnable類(ScheduledMethodRunnable類實(shí)現(xiàn)了Runnable接口),并把其做為一個(gè)任務(wù)注冊(cè)到ScheduledTaskRegistrar中。
如果對(duì)具體的邏輯感興趣,可以從postProcessAfterInitialization方法順著processScheduled方法一次debug。
前面我們介紹通過BeanPostProcessor解析出了所有的任務(wù),接下來要做的事情就是提交任務(wù)了。
@Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.applicationContext) { // Running in an ApplicationContext -> register tasks this late... // giving other ContextRefreshedEvent listeners a chance to perform // their work at the same time (e.g. Spring Batch's job registration). finishRegistration(); } }
ScheduledAnnotationBeanPostProcessor監(jiān)聽的事件是ContextRefreshedEvent,就是在容器初始化,或者刷新的時(shí)候被調(diào)用。
監(jiān)聽到ContextRefreshedEvent事件之后,值調(diào)用了finishRegistration方法,這個(gè)方法的基本流程如下:
找到容器中的SchedulingConfigurer,并調(diào)用它的configureTasks,SchedulingConfigurer的作用主要就是配置ScheduledTaskRegistrar類,例如線程池等參數(shù),例如:
import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; @Configuration public class MyScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }
調(diào)用ScheduledTaskRegistrar的afterPropertiesSet方法執(zhí)行任務(wù),如果對(duì)具體的邏輯感興趣,可以閱讀ScheduledTaskRegistrar的scheduleTasks方法。
看完上述內(nèi)容,你們對(duì)SpringBoot 中@Schedule的原理是什么有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。
當(dāng)前標(biāo)題:SpringBoot中@Schedule的原理是什么
分享路徑:http://www.rwnh.cn/article32/jepipc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷、網(wǎng)站排名、小程序開發(fā)、動(dòng)態(tài)網(wǎng)站、建站公司、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)