@Async异步调用
项目中实现异步调用的方式还是挺多的,大多要么是使用ExecutorService要么是使用@Async注解,要么干脆直接手动创建线程。之前我自己都是使用ExecutorService去实现多线程的业务操作,但是奈何阿里巴巴归约扫描,看着难受。不用@Async不就是图个懒得写配置省事的。另外其中各自的优缺点这里就不再赘述了,本身也就是记录下自己使用的业务背景和代码配置情况吧。
业务背景: 实现的功能是Excel数据导入到数据库,由于使用的是SpringCloud,都是服务间的调用。所以基本的操作就是A服务接收上传的数据整理成业务参数后,调用B服务去进行落入数据库的操作。因为数据量较大,B服务无法在规定的超时时间内给予A服务响应,导致调用异常。故此考虑将数据平均分组,使用多线程异步去解决,同时在B服务内也使用多线程去进行数据库插入操作(后来改成了批量插入,效率更好,减少了数据库频繁访问连接的开销)。
代码:
@Bean(“taskExecutorOne”) :将配置的策略实例化,并命名,交给容器去管理。用的时候直接指定名字就可以了。
这里针对不同的业务场景,配置不同的策略,看自己的实际需求吧。
@EnableAsync
@Configuration
public class TaskPoolConfig {
@Bean("taskExecutorOne")
public Executor taskExecutorOne() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutorOne-");
/* 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
/* 说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,
用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,
这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,
这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,
如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 */
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Bean("taskExecutorTwo")
public Executor taskExecutorTwo() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(50);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutorTwo-");
/* 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
/* 说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,
用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,
这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,
这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,
如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 */
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Bean("taskExecutorThree")
public Executor taskExecutorThree() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutorThree-");
/* 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
/* 说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,
用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,
这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,
这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,
如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 */
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Bean("taskExecutorFour")
public Executor taskExecutorFour() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutorFour-");
/* 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
/* 说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,
用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,
这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,
这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,
如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 */
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Bean("taskExecutorFive")
public Executor taskExecutorFive() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutorFive-");
/* 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务 */
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
/* 说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,
用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,
这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,
这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,
如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 */
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
这里在插入数据库的方法上添加@Async注解,注解内指定要使用策略的实例的名称即可。
@Async("taskExecutorOne")
public void addData(User user) {
userMapper.insert(user);
}