文章目录
异步任务
1. @EnableAsync
@SpringBootApplication
@EnableAsync
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@EnableAsync Spring默认选择线程池的过程
- 在Spring的上下文中搜索类型为TaskExecutor或者名称为“taskExecutor”的bean,当可以找到的时候,就将任务提交到此线程池中执行。
- 当不存在以上线程池的时候,Spring会手动创建一个SimpleAsyncTaskExecutor执行异步任务。
- 当标识@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 即表示第二个周三(前面表示周几,后面表示第几个)
常用表达式例子
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2021-2022 表示2021-2022年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2021 2021年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2021-2022 2021年至2022年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发