SpringBoot 异步任务和定时任务 配置及使用

9 篇文章 0 订阅
8 篇文章 0 订阅

异步任务

1. @EnableAsync

@SpringBootApplication
@EnableAsync
public class SpringbootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}

@EnableAsync Spring默认选择线程池的过程
在这里插入图片描述

  1. 在Spring的上下文中搜索类型为TaskExecutor或者名称为“taskExecutor”的bean,当可以找到的时候,就将任务提交到此线程池中执行。
  2. 当不存在以上线程池的时候,Spring会手动创建一个SimpleAsyncTaskExecutor执行异步任务。
  3. 当标识@async注解的时候指定了,value值,Spring会使用指定的线程池执行。如@Async(value = “xxxPool”), Spring会去上下文中找名字为xxxPool的bean,并执行异步任务,找不到,会抛出异常。

TaskExecutionAutoConfiguration
spring有很多自动配置,我们发现了TaskExecutionAutoConfiguration
在这里插入图片描述

import java.util.concurrent.Executor;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.task.TaskExecutorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}.
 *
 * @author Stephane Nicoll
 * @author Camille Vienot
 * @since 2.1.0
 */
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {

	/**
	 * Bean name of the application {@link TaskExecutor}.
	 */
	public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

	@Bean
	@ConditionalOnMissingBean
	public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
			ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
			ObjectProvider<TaskDecorator> taskDecorator) {
		TaskExecutionProperties.Pool pool = properties.getPool();
		TaskExecutorBuilder builder = new TaskExecutorBuilder();
		builder = builder.queueCapacity(pool.getQueueCapacity());
		builder = builder.corePoolSize(pool.getCoreSize());
		builder = builder.maxPoolSize(pool.getMaxSize());
		builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
		builder = builder.keepAlive(pool.getKeepAlive());
		Shutdown shutdown = properties.getShutdown();
		builder = builder.awaitTermination(shutdown.isAwaitTermination());
		builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
		builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
		builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
		builder = builder.taskDecorator(taskDecorator.getIfUnique());
		return builder;
	}

	@Lazy
	@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
			AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
	@ConditionalOnMissingBean(Executor.class) // 当Spring上下文中没有Executor对象实例时,则会使用这个创建一个默认的线程池, 
	public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
		return builder.build();
	}

}

TaskExecutionProperties

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {

	private final Pool pool = new Pool();

	private final Shutdown shutdown = new Shutdown();

	/**
	 * Prefix to use for the names of newly created threads.
	 */
	private String threadNamePrefix = "task-";

	public Pool getPool() {
		return this.pool;
	}

	public Shutdown getShutdown() {
		return this.shutdown;
	}

	public String getThreadNamePrefix() {
		return this.threadNamePrefix;
	}

	public void setThreadNamePrefix(String threadNamePrefix) {
		this.threadNamePrefix = threadNamePrefix;
	}

	public static class Pool {

		/**
		 * Queue capacity. An unbounded capacity does not increase the pool and therefore
		 * ignores the "max-size" property.
		 */
		private int queueCapacity = Integer.MAX_VALUE;

		/**
		 * Core number of threads.
		 */
		private int coreSize = 8;

		/**
		 * Maximum allowed number of threads. If tasks are filling up the queue, the pool
		 * can expand up to that size to accommodate the load. Ignored if the queue is
		 * unbounded.
		 */
		private int maxSize = Integer.MAX_VALUE;

		/**
		 * Whether core threads are allowed to time out. This enables dynamic growing and
		 * shrinking of the pool.
		 */
		private boolean allowCoreThreadTimeout = true;

		/**
		 * Time limit for which threads may remain idle before being terminated.
		 */
		private Duration keepAlive = Duration.ofSeconds(60);
   }
}

所以我们也可以通过配置文件指定线程池的配置

spring.task.execution.pool.core-size # 核心线程数,默认为8
spring.task.execution.pool.queue-capacity # 队列容量,默认为无限大
spring.task.execution.pool.max-size # 最大线程数,默认为无限大
spring.task.execution.pool.allow-core-thread-timeout #
是否允许回收空闲的线程,默认为true
spring.task.execution.pool.keep-alive #空闲的线程可以保留多少秒,默认为60。如果超过这个时间没有任务调度,则线程会被回收
spring.task.execution.thread-name-prefix # 线程名前缀,“task-”;

SimpleAsyncTaskExecutor

/**
 * {@link TaskExecutor} implementation that fires up a new Thread for each task,
 * executing it asynchronously.  为每个任务触发一个新线程, 异步执行
 *
 * <p>Supports limiting concurrent threads through the "concurrencyLimit"
 * bean property. By default, the number of concurrent threads is unlimited.
 * 支持通过“concurrencyLimit”限制并发线程 bean属性。缺省情况下,并发线程数是无限的。 
 * 
 * NOTE: This implementation does not reuse threads!
注意:这个实现不重用线程!
Consider a thread-pooling TaskExecutor implementation instead, in particular for
 * executing a large number of short-lived tasks.
 * 考虑线程池的TaskExecutor实现代替,特别是执行大量的短命任务。
 */
@SuppressWarnings("serial")
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
		implements AsyncListenableTaskExecutor, Serializable {
}

2. 指定异步任务执行的线程池

@Configuration
public class AsyncConfig implements AsyncConfigurer {


    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadFactory(new CustomizableThreadFactory("AsyncPool-"));
        threadPoolTaskExecutor.setMaxPoolSize(50);
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(1000);
        threadPoolTaskExecutor.setKeepAliveSeconds(5 * 60);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return threadPoolTaskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

定时任务

1. 使用@EnableScheduling 启动定时任务支持

@SpringBootApplication
@EnableScheduling
public class SpringbootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}

@EnableScheduling Spring默认选择线程池的过程
在这里插入图片描述

当Spring执行定时任务的时候,首先会在上下文中找类型为TaskScheduler或者名称为taskScheduler的bean,找不到的时候会手动创建一个线程执行此task。

TaskExecutionAutoConfiguration
spring有很多自动配置,我们发现了TaskExecutionAutoConfiguration
在这里插入图片描述

TaskSchedulingProperties

package org.springframework.boot.autoconfigure.task;

import java.time.Duration;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties("spring.task.scheduling")
public class TaskSchedulingProperties {

	private final Pool pool = new Pool();

	private final Shutdown shutdown = new Shutdown();

	/**
	 * Prefix to use for the names of newly created threads.
	 */
	private String threadNamePrefix = "scheduling-";

	public Pool getPool() {
		return this.pool;
	}

	public Shutdown getShutdown() {
		return this.shutdown;
	}

	public String getThreadNamePrefix() {
		return this.threadNamePrefix;
	}

	public void setThreadNamePrefix(String threadNamePrefix) {
		this.threadNamePrefix = threadNamePrefix;
	}

	public static class Pool {

		/**
		 * Maximum allowed number of threads.
		 */
		private int size = 1;

		public int getSize() {
			return this.size;
		}

		public void setSize(int size) {
			this.size = size;
		}

	}

	public static class Shutdown {

		/**
		 * Whether the executor should wait for scheduled tasks to complete on shutdown.
		 */
		private boolean awaitTermination;

		/**
		 * Maximum time the executor should wait for remaining tasks to complete.
		 */
		private Duration awaitTerminationPeriod;

		public boolean isAwaitTermination() {
			return this.awaitTermination;
		}

		public void setAwaitTermination(boolean awaitTermination) {
			this.awaitTermination = awaitTermination;
		}

		public Duration getAwaitTerminationPeriod() {
			return this.awaitTerminationPeriod;
		}

		public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) {
			this.awaitTerminationPeriod = awaitTerminationPeriod;
		}

	}

}

所以我们也可以通过配置文件指定线程池的配置

spring.task.scheduling.pool.size # Maximum allowed number of threads. 默认1
spring.task.scheduling.thread-name-prefix # 线程名前缀,默认 “scheduling-”;

2. 指定定时任务执行的线程池

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        TaskScheduler taskScheduler = taskScheduler();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }

    /**
     * 并行任务使用策略:多线程处理(配置线程数等)
     * @return
     */
    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(20);
        //设置线程名开头
        scheduler.setThreadNamePrefix("Scheduling-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }

}

@Scheduled 常见用法

/**
     * 表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
     */
    @Scheduled(fixedDelay = 500)
    public void fixedDelay() {
        log.info("fixedDelay 上一次任务执行完成后500ms再次执行, time:{}", LocalDateTime.now());
    }

    /**
     * 表示按一定的频率执行任务,参数类型为long,单位ms;
     */
    @Scheduled(fixedRate = 500)
    public void fixedRate() {
        log.info("fixedRate 一定的频率500ms执行任务, time:{}", LocalDateTime.now());
    }

    /**
     * cron表达式,指定任务在特定时间执行;
     */
    @Scheduled(cron = "*/${time.interval} * * * * ?")
    public void cron3() {
        log.info("cron3 time.interval执行任务, time:{}", LocalDateTime.now());
    }

    /**
     * cron表达式,指定任务在特定时间执行;
     */
    @Scheduled(cron = "0/6 * * * * ?")
    public void cron() {
        log.info("cron 0 50 * * * ?执行任务, time:{}", LocalDateTime.now());
    }

    /**
     * 第一次1秒后执行,当执行完后2秒再执行
     */
    @Scheduled(initialDelay = 1000, fixedDelay = 2000)
    public void initialDelay() {
        log.info("initialDelay 第一次1秒后执行,当执行完后2秒再执行, time:{}", LocalDateTime.now());
    }

    /**
     * 可以从配置文件中读取
     */
    @Scheduled(fixedDelayString = "${jobs.fixedDelay}")
    public void fixedDelayString() {
        log.info("fixedDelayString 执行, time:{}", LocalDateTime.now());
    }


    @Scheduled(cron = "${job.cron}")
    public void cron2() {
        log.info("cron2 from application.properties 执行任务, time:{}", LocalDateTime.now());
    }

    /**
     * cron:cron表达式,指定任务在特定时间执行;
     * fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
     * fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
     * fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
     * fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
     * initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
     * initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
     */

附:Cron表达式

这里的cron表达式即为了描述任务执行的时间规则。cron由6-7个元素组成,他们之间使用空格来分割,以此代表:
秒:0~59
分:0~59
时:0~23
日:1~31(具体月的最后一天也可能是30)
月:1~12
星期:1~7(注意1为周日)
年:1970~2099

*:表示任意一个值都会触发,七个元素位置中都可以出现,如在分钟出现即表示每分钟都回去执行
?:和*类似,但只会出现在日和星期(这两个位置只能有一个是具有意义的描述,如:要么是每个月的3号要么是每个月的每个星期的周三),当其中一个配置了有意义的值,另一个写?
-:表示范围(至),七个元素位置中都可以出现,如分钟里出现1-7则表示1分钟到7分钟每分钟执行一次
/:表示从开始时间每隔多长时间执行一次,七个元素位置中都可以出现,如分钟里出现1/7则表示1分触发一次1+7=8分触发一次
,:表示枚举值,七个元素位置中都可以出现,如分钟里出现1,7则表示1分和7分各执行一次
L:表示最后,只会出现在日和星期位置,日表示最后一天,星期则会搭配数字如5L表示最后一个周四
W:表示有效工作日(周一到周五),只会出现在日期里,前面会搭配数字,如5W:如果5号是周六那么执行时间为周五即4号;如果5号是周日那么执行时间为下周一即6号;(即系统会推荐离今天最近的一个工作日,但注意不会进行跨越查找:如果是31W且31号是周日,那么会在29号执行)
LW:连用表示最后一个工作日,只会出现在日期里
“#”:(前后要加数字)表示第几个星期几,只会出现在星期里,如4#2 即表示第二个周三(前面表示周几,后面表示第几个)

常用表达式例子

10/2 * * * * ?   表示每2秒 执行任务

  (10 0/2 * * * ?    表示每2分钟 执行任务

  (10 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务

  (20 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业

  (30 15 10 ? 6L 2021-2022   表示2021-2022年的每个月的最后一个星期五上午10:15执行作

  (40 0 10,14,16 * * ?   每天上午10点,下午2点,4点 

  (50 0/30 9-17 * * ?   朝九晚五工作时间内每半小时 

  (60 0 12 ? * WED    表示每个星期三中午12点 

  (70 0 12 * * ?   每天中午12点触发 

  (80 15 10 ? * *    每天上午10:15触发 

  (90 15 10 * * ?     每天上午10:15触发 

  (100 15 10 * * ?    每天上午10:15触发 

  (110 15 10 * * ? 2021    2021年的每天上午10:15触发 

  (120 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发 

  (130 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发 

  (140 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 

  (150 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发 

  (160 10,44 14 ? 3 WED    每年三月的星期三的下午2:102:44触发 

  (170 15 10 ? * MON-FRI    周一至周五的上午10:15触发 

  (180 15 10 15 * ?    每月15日上午10:15触发 

  (190 15 10 L * ?    每月最后一日的上午10:15触发 

  (200 15 10 ? * 6L    每月的最后一个星期五上午10:15触发 

  (210 15 10 ? * 6L 2021-2022   2021年至2022年的每月的最后一个星期五上午10:15触发 

  (220 15 10 ? * 6#3   每月的第三个星期五上午10:15触发
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值