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

SpringBoot中定時(shí)任務(wù)@Scheduled注解的使用解讀

 更新時(shí)間:2022年09月30日 14:15:22   作者:Sunshine_Dongyang  
這篇文章主要介紹了SpringBoot中定時(shí)任務(wù)@Scheduled注解的使用解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

項(xiàng)目開發(fā)中,經(jīng)常會遇到定時(shí)任務(wù)的場景,Spring提供了@Scheduled注解,方便進(jìn)行定時(shí)任務(wù)的開發(fā)

概述

要使用@Scheduled注解,首先需要在啟動類添加@EnableScheduling,啟用Spring的計(jì)劃任務(wù)執(zhí)行功能,這樣可以在容器中的任何Spring管理的bean上檢測@Scheduled注解,執(zhí)行計(jì)劃任務(wù)

注解定義

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {

	String cron() default "";

	String zone() default "";

	long fixedDelay() default -1;

	String fixedDelayString() default "";

	long fixedRate() default -1;

	String fixedRateString() default "";
	
	long initialDelay() default -1;

	String initialDelayString() default "";
}

參數(shù)說明

參數(shù)參數(shù)說明示例
cron任務(wù)執(zhí)行的cron表達(dá)式0/1 * * * * ?
zonecron表達(dá)時(shí)解析使用的時(shí)區(qū),默認(rèn)為服務(wù)器的本地時(shí)區(qū),使用java.util.TimeZone#getTimeZone(String)方法解析GMT-8:00
fixedDelay上一次任務(wù)執(zhí)行結(jié)束到下一次執(zhí)行開始的間隔時(shí)間,單位為ms1000
fixedDelayString上一次任務(wù)執(zhí)行結(jié)束到下一次執(zhí)行開始的間隔時(shí)間,使用java.time.Duration#parse解析PT15M
fixedRate以固定間隔執(zhí)行任務(wù),即上一次任務(wù)執(zhí)行開始到下一次執(zhí)行開始的間隔時(shí)間,單位為ms,若在調(diào)度任務(wù)執(zhí)行時(shí),上一次任務(wù)還未執(zhí)行完畢,會加入worker隊(duì)列,等待上一次執(zhí)行完成后立即執(zhí)行下一次任務(wù)2000
fixedRateString與fixedRate邏輯一致,只是使用java.time.Duration#parse解析PT15M
initialDelay首次任務(wù)執(zhí)行的延遲時(shí)間1000
initialDelayString首次任務(wù)執(zhí)行的延遲時(shí)間,使用java.time.Duration#parse解析PT15M

源碼解析

配置了@Scheduled注解的方法,Spring的處理是通過注冊ScheduledAnnotationBeanPostProcessor來執(zhí)行,將不同配置參數(shù)的任務(wù)分配給不同的handler處理,核心代碼如下

org.springframework.scheduling.annotation.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;
}

org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled

/**
 * Process the given {@code @Scheduled} method declaration on the given bean.
 * @param scheduled the @Scheduled annotation
 * @param method the method that the annotation has been declared on
 * @param bean the target bean instance
 * @see #createRunnable(Object, Method)
 */
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
	try {
		Runnable runnable = createRunnable(bean, method);
		boolean processedSchedule = false;
		String errorMessage =
				"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
		Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
		// Determine initial delay
		long initialDelay = scheduled.initialDelay();
		String initialDelayString = scheduled.initialDelayString();
		if (StringUtils.hasText(initialDelayString)) {
			Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
			if (this.embeddedValueResolver != null) {
				initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
			}
			if (StringUtils.hasLength(initialDelayString)) {
				try {
					initialDelay = parseDelayAsLong(initialDelayString);
				}
				catch (RuntimeException ex) {
					throw new IllegalArgumentException(
							"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
				}
			}
		}
		// Check cron expression
		String cron = scheduled.cron();
		if (StringUtils.hasText(cron)) {
			String zone = scheduled.zone();
			if (this.embeddedValueResolver != null) {
				cron = this.embeddedValueResolver.resolveStringValue(cron);
				zone = this.embeddedValueResolver.resolveStringValue(zone);
			}
			if (StringUtils.hasLength(cron)) {
				Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
				processedSchedule = true;
				if (!Scheduled.CRON_DISABLED.equals(cron)) {
					TimeZone timeZone;
					if (StringUtils.hasText(zone)) {
						timeZone = StringUtils.parseTimeZoneString(zone);
					}
					else {
						timeZone = TimeZone.getDefault();
					}
					tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
				}
			}
		}
		// At this point we don't need to differentiate between initial delay set or not anymore
		if (initialDelay < 0) {
			initialDelay = 0;
		}
		// Check fixed delay
		long fixedDelay = scheduled.fixedDelay();
		if (fixedDelay >= 0) {
			Assert.isTrue(!processedSchedule, errorMessage);
			processedSchedule = true;
			tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
		}
		String fixedDelayString = scheduled.fixedDelayString();
		if (StringUtils.hasText(fixedDelayString)) {
			if (this.embeddedValueResolver != null) {
				fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
			}
			if (StringUtils.hasLength(fixedDelayString)) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				try {
					fixedDelay = parseDelayAsLong(fixedDelayString);
				}
				catch (RuntimeException ex) {
					throw new IllegalArgumentException(
							"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
				}
				tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
			}
		}
		// Check fixed rate
		long fixedRate = scheduled.fixedRate();
		if (fixedRate >= 0) {
			Assert.isTrue(!processedSchedule, errorMessage);
			processedSchedule = true;
			tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
		}
		String fixedRateString = scheduled.fixedRateString();
		if (StringUtils.hasText(fixedRateString)) {
			if (this.embeddedValueResolver != null) {
				fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
			}
			if (StringUtils.hasLength(fixedRateString)) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				try {
					fixedRate = parseDelayAsLong(fixedRateString);
				}
				catch (RuntimeException ex) {
					throw new IllegalArgumentException(
							"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
				}
				tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
			}
		}
		// Check whether we had any attribute set
		Assert.isTrue(processedSchedule, errorMessage);
		// Finally register the scheduled tasks
		synchronized (this.scheduledTasks) {
			Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
			regTasks.addAll(tasks);
		}
	}
	catch (IllegalArgumentException ex) {
		throw new IllegalStateException(
				"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
	}
}

org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks

/**
 * Schedule all registered tasks against the underlying
 * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
 */
proected void scheduleTasks() {
	if (this.taskScheduler == null) {
		this.localExecutor = Executors.newSingleThreadScheduledExecutor();
		this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
	}
	if (this.triggerTasks != null) {
		for (TriggerTask task : this.triggerTasks) {
			addScheduledTask(scheduleTriggerTask(task));
		}
	}
	if (this.cronTasks != null) {
		for (CronTask task : this.cronTasks) {
			addScheduledTask(scheduleCronTask(task));
		}
	}
	if (this.fixedRateTasks != null) {
		for (IntervalTask task : this.fixedRateTasks) {
			addScheduledTask(scheduleFixedRateTask(task));
		}
	}
	if (this.fixedDelayTasks != null) {
		for (IntervalTask task : this.fixedDelayTasks) {
			addScheduledTask(scheduleFixedDelayTask(task));
		}
	}
}

使用詳解

定時(shí)任務(wù)同步/異步執(zhí)行

定時(shí)任務(wù)執(zhí)行默認(rèn)是單線程模式,會創(chuàng)建一個(gè)本地線程池,線程池大小為1。當(dāng)項(xiàng)目中有多個(gè)定時(shí)任務(wù)時(shí),任務(wù)之間會相互等待,同步執(zhí)行

源碼:

// org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
if (this.taskScheduler == null) {
    this.localExecutor = Executors.newSingleThreadScheduledExecutor();
    this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}

// java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

代碼示例:

@Slf4j
@Component
public class RunIntervalTestScheduler {

    @Scheduled(cron = "0/1 * * * * ?")
    public void singleThreadTest1() {
        log.info("singleThreadTest1");
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void singleThreadTest2() {
        log.info("singleThreadTest2");
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void singleThreadTest3() {
        log.info("singleThreadTest3");
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    }
}

執(zhí)行結(jié)果:

可以看到,默認(rèn)情況下,三個(gè)任務(wù)串行執(zhí)行,都使用pool-1-thread-1同一個(gè)線程池,并且線程只有一個(gè)

可以通過實(shí)現(xiàn)SchedulingConfigurer接口,手動創(chuàng)建線程池,配置期望的線程數(shù)量

示例代碼:

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {

    /**
     * 任務(wù)執(zhí)行線程池大小
     */
    private static final int TASK_POOL_SIZE = 50;
    /**
     * 線程名
     */
    private static final String TASK_THREAD_PREFIX = "test-task-";

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler taskPool = new ThreadPoolTaskScheduler();
        taskPool.setPoolSize(TASK_POOL_SIZE);
        taskPool.setThreadNamePrefix(TASK_THREAD_PREFIX);
        taskPool.initialize();
        scheduledTaskRegistrar.setTaskScheduler(taskPool);
    }
}

任務(wù)執(zhí)行結(jié)果:

此時(shí)任務(wù)的執(zhí)行已經(jīng)異步化,從自定義線程池中分配線程執(zhí)行任務(wù),在實(shí)際應(yīng)用中需要考慮實(shí)際任務(wù)數(shù)量,創(chuàng)建相應(yīng)大小的線程池

fixedRate/fixedDelay區(qū)別

fixedRate是配置上一次任務(wù)執(zhí)行開始到下一次執(zhí)行開始的間隔時(shí)間,不會等待上一次任務(wù)執(zhí)行完成就會調(diào)度下一次任務(wù),將其放入等待隊(duì)列中

代碼示例:

@Slf4j
@Component
public class RunIntervalTestScheduler {

    @Scheduled(initialDelay = 1000, fixedRate = 1000)
    public void fixedRate() throws Exception {
        log.info("fixedRate run");
        TimeUnit.SECONDS.sleep(3);
    }
}

執(zhí)行結(jié)果:

任務(wù)配置的fixedRate為1s,執(zhí)行日志打印的時(shí)間間隔都是3s左右,也就是上一次執(zhí)行完成后,緊接著就執(zhí)行下一次任務(wù)

fixedDelay是配置的上一次任務(wù)執(zhí)行結(jié)束到下一次執(zhí)行開始的間隔時(shí)間,也就是說會等待上一次任務(wù)執(zhí)行結(jié)束后,延遲間隔時(shí)間,再執(zhí)行下一次任務(wù)

代碼示例:

@Slf4j
@Component
public class RunIntervalTestScheduler {

    @Scheduled(initialDelay = 1000, fixedDelay = 1000)
    public void fixedDelay() throws Exception {
        log.info("fixedDelay run");
        TimeUnit.SECONDS.sleep(3);
    }
}

執(zhí)行結(jié)果:

任務(wù)配置的fixedDelay為1s,執(zhí)行日志打印的時(shí)間間隔都是4s左右,也就是上一次執(zhí)行完成后,延遲1s后執(zhí)行下一次任務(wù)

cron表達(dá)式如果配置為類似每秒執(zhí)行、每分鐘執(zhí)行(例:0/1 * * * * ?, 每秒執(zhí)行),調(diào)度跟fixedDelay是一致的,也是在上一次任務(wù)執(zhí)行結(jié)束后,等待間隔時(shí)間

代碼示例:

@Slf4j
@Component
public class RunIntervalTestScheduler {

    @Scheduled(cron = "0/1 * * * * ?")
    public void cronRun() throws Exception{
        log.info("cron run");
        TimeUnit.SECONDS.sleep(3);
    }
}

執(zhí)行結(jié)果:

執(zhí)行日志打印的時(shí)間間隔都是4s左右,也就是上一次執(zhí)行完成后,延遲1s后執(zhí)行下一次任務(wù)

cron表達(dá)式如果配置為固定時(shí)間執(zhí)行(例:1 * * * * ?, 秒數(shù)為1時(shí)執(zhí)行),若上一次任務(wù)沒有執(zhí)行完,則不會調(diào)度本次任務(wù),跳過本次執(zhí)行,等待下一次執(zhí)行周期

代碼示例:

@Slf4j
@Component
public class RunIntervalTestScheduler {

    @Scheduled(cron = "1 * * * * ?")
    public void cronRun() throws Exception{
        log.info("cron run");
        TimeUnit.SECONDS.sleep(70);
    }
}

執(zhí)行結(jié)果:

上一次任務(wù)未執(zhí)行完畢,則跳過了本次執(zhí)行

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

相關(guān)文章

  • Java線程池7個(gè)參數(shù)的詳細(xì)含義

    Java線程池7個(gè)參數(shù)的詳細(xì)含義

    java多線程開發(fā)時(shí),常常用到線程池技術(shù),這篇文章是對創(chuàng)建java線程池時(shí)的七個(gè)參數(shù)的詳細(xì)解釋,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • java實(shí)現(xiàn)刪除某條信息并刷新當(dāng)前頁操作

    java實(shí)現(xiàn)刪除某條信息并刷新當(dāng)前頁操作

    這篇文章主要介紹了java實(shí)現(xiàn)刪除某條信息并刷新當(dāng)前頁操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 利用Java判斷一個(gè)字符串是否包含某個(gè)字符

    利用Java判斷一個(gè)字符串是否包含某個(gè)字符

    在Java編程中,字符串操作是日常開發(fā)的常見任務(wù),涉及判斷、查找、替換等多種操作,文章介紹了如何在Java中判斷字符串是否包含某字符,提供了使用contains方法和字符數(shù)組遍歷兩種基礎(chǔ)方法,需要的朋友可以參考下
    2024-11-11
  • Java用自帶的Image IO給圖片添加水印

    Java用自帶的Image IO給圖片添加水印

    本文主要介紹了如何采用Java自帶的Image IO實(shí)現(xiàn)圖片添加水印的需求,并整合了一些其他功能,感興趣的朋友可以參考下
    2021-06-06
  • 解決get請求入?yún)NotNull驗(yàn)證不生效問題

    解決get請求入?yún)NotNull驗(yàn)證不生效問題

    這篇文章主要介紹了解決get請求入?yún)NotNull驗(yàn)證不生效問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java用鄰接表存儲圖的示例代碼

    Java用鄰接表存儲圖的示例代碼

    鄰接表是圖的一種鏈?zhǔn)酱鎯Ψ椒?,其?shù)據(jù)結(jié)構(gòu)包括兩部分:節(jié)點(diǎn)和鄰接點(diǎn)。本文將用鄰接表實(shí)現(xiàn)存儲圖,感興趣的小伙伴可以了解一下
    2022-06-06
  • 詳解Spring AOP 實(shí)現(xiàn)“切面式”valid校驗(yàn)

    詳解Spring AOP 實(shí)現(xiàn)“切面式”valid校驗(yàn)

    本篇文章主要介紹了詳解Spring AOP 實(shí)現(xiàn)“切面式”valid校驗(yàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • Spring?IOC容器基于XML外部屬性文件的Bean管理

    Spring?IOC容器基于XML外部屬性文件的Bean管理

    這篇文章主要為大家介紹了Spring?IOC容器Bean管理XML外部屬性文件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Java枚舉抽象方法實(shí)例解析

    Java枚舉抽象方法實(shí)例解析

    這篇文章主要介紹了Java枚舉抽象方法實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • 簡單的理解java集合中的HashSet和HashTree幾個(gè)重寫方法

    簡單的理解java集合中的HashSet和HashTree幾個(gè)重寫方法

    這篇文章主要介紹了簡單的理解java集合中的HashSet和HashTree幾個(gè)重寫方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10

最新評論