我们都知道,java中有ThreadPoolExecutor提供的线程池服务,非常好用。可以有效的解决了一些异步业务,提高系统性能。但是java中配置和使用线程池有点繁琐,而在springboot中,线程池的配置简直就是轻而易举。下面直接上干货。
springboot中主要使用配置类来配置线程池
@Async注解可以使用配置好的线程池
其他的配置例如yml或者properties文件,按照springboot的正常配置就行,没什么特殊的。当然了,如果springboot不熟悉的,建议先去百度或者Google看一下springboot的入门教程。
下面我们来详细看一下配置和使用的过程
1、配置线程池
主要使用到 @Configuration @EnableAsync
这两个注解,从字面上可以看出,前者是自定义配置类,后者是使能线程池。下面是我的一个配置类示例:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; /** * @author zhaoxi * @date 2018/11/16 14:33 * TODO:线程池的配置 */ @Configuration @EnableAsync public class ExecutorConfig { private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class); /** * TODO: 此方法名称为asyncPromiseExecutor,即在spring中注入了一个名字为asyncPromiseExecutor的bean * 方法名只要在项目中唯一性,可以适当任意取(最好遵循一定的规则) * 使用方法:在需要加入线程池的方法上增加注解@Async("asyncPromiseExecutor")就可以加入此线程池异步执行 * * @return * @throws * @author zhaoxi * @time 2018/11/16 14:36 * @params */ @Bean public Executor asyncPromiseExecutor() { logger.info("start asyncPromiseExecutor"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置核心线程数 executor.setCorePoolSize(5); //配置最大线程数 executor.setMaxPoolSize(10); //配置队列大小 executor.setQueueCapacity(99999); //配置线程池中的线程的名称前缀 executor.setThreadNamePrefix("async-promise-"); /** * rejection-policy:当pool已经达到max size的时候,如何处理新任务 * CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 */ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //初始化执行器 executor.initialize(); return executor; } }
只听到从知秋君办公室传来知秋君的声音: 燕子又将春色去,纱窗一阵黄昏雨。有谁来对上联或下联?
2、使用线程池
刚才我们在配置类定义了一个方法,并且使用了@Bean
,故而spring中注入了一个名字为asyncPromiseExecutor的bean。故而我们只需要去某个实现类的方法上加上springboot提供的@Async注解即可;注解中指明要加入的线程池,例如 @Async(“asyncPromiseExecutor”)
示例:
controller层
此代码由一叶知秋网-知秋君整理@ApiOperation("分享游戏") @PostMapping("/share/xxx/game") public PaixiResult promiseShareGame(String token) { if (Util.isEmpty(token)) { return ResponseUtils.build(401, "请传入token"); } log.info("开始判断是否首次分享游戏"); promiseService.promiseShareGame(token); log.info("成功提交到线程池,本请求可以返回了"); return ResponseUtils.SUCCESS(token); }
service层
接口
/** * TODO: 分享游戏 * 丢到线程池里面异步执行 * * @return * @throws * @author zhaoxi * @time 2018/11/16 14:27 * @params */ void promiseShareGame(String token);
实现
此代码由一叶知秋网-知秋君整理@Override @Async("asyncPromiseExecutor") //此处方法实现被加入到线程池中执行。当前的方法立刻返回,上级调用即可结束 public void promiseShareGame(String token) { //获取userId String userId = userFeign.getUserIdByToken(token); if (Util.isEmpty(userId)) { return; } /** * 耗时试验 */ log.info("开始耗时等待"); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } log.info("耗时等待结束,线程结束"); }
注意:
设置加入到线程池的方法不应该有返回值,虽然定义为有返回值程序执行无错误,但是定义为线程的方法,上级调用者无需等待处理结果,如果调用者强行等待返回结果,此异步设置将会没有意义,系统仍然会按照同步的逻辑处理。读者可以自己动手试验一下!
3、知识总结
corePoolSize,maximumPoolSize,workQueue之间关系。
1).当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2).当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3).当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4).当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5).当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6).当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭