欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot @Schedule的使用注意與原理分析

 更新時(shí)間:2024年08月02日 11:22:21   作者:trayvontang  
這篇文章主要介紹了SpringBoot @Schedule的使用注意與原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

簡介

之前使用@Schedule一直沒有遇到什么問題,那種拿來就用的感覺還挺好,最近使用@Schedule遇到一點(diǎn)問題,才仔細(xì)的研究了一下@Schedule的一些細(xì)節(jié)和原理問題。

這篇文章就將分享一下,使用@Schedule一些可能被忽略的問題。

注意事項(xiàng)

@Schedule默認(rèn)線程池大小

我相信@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í)間比較長,大于了設(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 @Schedule原理

在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類。

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封裝任務(wù)

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;
}

簡單說一下流程:

找到所有的Schedule方法,把它封裝為ScheduledMethodRunnable類(ScheduledMethodRunnable類實(shí)現(xiàn)了Runnable接口),并把其做為一個(gè)任務(wù)注冊(cè)到ScheduledTaskRegistrar中。

如果對(duì)具體的邏輯感興趣,可以從postProcessAfterInitialization方法順著processScheduled方法一次debug。

ApplicationListener執(zhí)行任務(wù)

前面我們介紹通過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è)方法的基本流程如下:

  • 1.找到容器中的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));
    }
}
  • 2.調(diào)用ScheduledTaskRegistrar的afterPropertiesSet方法執(zhí)行任務(wù),如果對(duì)具體的邏輯感興趣,可以閱讀ScheduledTaskRegistrar的scheduleTasks方法。

關(guān)于為啥直接在容器中注入一個(gè)TaskScheduler、ScheduledExecutorService也可以有效,也可以在finishRegistration方法中找到答案。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Gson中@JsonAdater注解的幾種方式總結(jié)

    Gson中@JsonAdater注解的幾種方式總結(jié)

    這篇文章主要介紹了Gson中@JsonAdater注解的幾種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Java Map.Entry的使用方法解析

    Java Map.Entry的使用方法解析

    這篇文章主要介紹了Java Map.Entry的使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java19新特性虛擬線程的具體使用

    Java19新特性虛擬線程的具體使用

    Java 19 引入了虛擬線程,這是 JDK Project Loom 項(xiàng)目中的重要新特性,目的是簡化 Java 中的并發(fā)編程,并提高線程管理的效率和性能,下面就來具體介紹下
    2024-09-09
  • IDEA中切換不同版本的JDK的詳細(xì)教程(超管用)

    IDEA中切換不同版本的JDK的詳細(xì)教程(超管用)

    這篇文章主要介紹了IDEA中切換不同版本的JDK的詳細(xì)教程(超管用),本文通過步驟詳解給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • java實(shí)現(xiàn)將結(jié)果集封裝到List中的方法

    java實(shí)現(xiàn)將結(jié)果集封裝到List中的方法

    這篇文章主要介紹了java實(shí)現(xiàn)將結(jié)果集封裝到List中的方法,涉及java數(shù)據(jù)庫查詢及結(jié)果集轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2016-07-07
  • springboot 使用yml配置文件給靜態(tài)變量賦值教程

    springboot 使用yml配置文件給靜態(tài)變量賦值教程

    這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • 淺談在eclipse中如何修改svn的用戶名和密碼

    淺談在eclipse中如何修改svn的用戶名和密碼

    這篇文章主要介紹了在eclipse中如何修改svn的用戶名和密碼的方法,在eclipse中經(jīng)常用svn進(jìn)行代碼版本控制,提交或更新代碼的時(shí)候需要我們輸入用戶名和密碼。對(duì)此感興趣的話可以來了解一下
    2020-07-07
  • Java對(duì)List進(jìn)行排序的方法總結(jié)

    Java對(duì)List進(jìn)行排序的方法總結(jié)

    在Java中,對(duì)List進(jìn)行排序是一項(xiàng)常見的任務(wù),Java提供了多種方法來對(duì)List中的元素進(jìn)行排序,本文將詳細(xì)介紹如何使用Java來實(shí)現(xiàn)List的排序操作,涵蓋了常用的排序方法和技巧,需要的朋友可以參考下
    2024-07-07
  • SpringBoot項(xiàng)目實(shí)現(xiàn)統(tǒng)一異常處理的最佳方案

    SpringBoot項(xiàng)目實(shí)現(xiàn)統(tǒng)一異常處理的最佳方案

    在前后端分離的項(xiàng)目開發(fā)過程中,我們通常會(huì)對(duì)數(shù)據(jù)返回格式進(jìn)行統(tǒng)一的處理,這樣可以方便前端人員取數(shù)據(jù),后端發(fā)生異常時(shí)同樣會(huì)使用此格式將異常信息返回給前端,本文介紹了如何在SpringBoot項(xiàng)目中實(shí)現(xiàn)統(tǒng)一異常處理,如有錯(cuò)誤,還望批評(píng)指正
    2024-02-02
  • Spring整合mybatis、springMVC總結(jié)

    Spring整合mybatis、springMVC總結(jié)

    這篇文章主要為大家詳細(xì)介紹了Java整合Mybatis,SpringMVC,文中有詳細(xì)的代碼示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2023-05-05

最新評(píng)論