javascript
@async 默认线程池_Springboot线程池的使用和扩展
點擊上方“Java知音”,選擇“置頂公眾號”
技術(shù)文章第一時間送達!
作者:程序員欣宸
https://blog.csdn.net/boling_cavalry
技術(shù)經(jīng)驗交流:點擊入群
我們常用ThreadPoolExecutor提供的線程池服務,springboot框架提供了@Async注解,幫助我們更方便的將業(yè)務邏輯提交到線程池中異步執(zhí)行,今天我們就來實戰(zhàn)體驗這個線程池服務;
實戰(zhàn)環(huán)境
windowns10;
jdk1.8;
springboot 1.5.9.RELEASE;
開發(fā)工具:IntelliJ IDEA;
實戰(zhàn)源碼
本次實戰(zhàn)的源碼可以在我的GitHub下載,地址:
git@github.com:zq2599/blog_demos.git,
項目主頁:
https://github.com/zq2599/blog_demos
這里面有多個工程,本次用到的工程為threadpooldemoserver,如下圖紅框所示:
實戰(zhàn)步驟梳理
本次實戰(zhàn)的步驟如下:
創(chuàng)建springboot工程;
創(chuàng)建Service層的接口和實現(xiàn);
創(chuàng)建controller,開發(fā)一個http服務接口,里面會調(diào)用service層的服務;
創(chuàng)建線程池的配置;
將Service層的服務異步化,這樣每次調(diào)用都會都被提交到線程池異步執(zhí)行;
擴展ThreadPoolTaskExecutor,在提交任務到線程池的時候可以觀察到當前線程池的情況;
創(chuàng)建springboot工程
用IntelliJ IDEA創(chuàng)建一個springboot的web工程threadpooldemoserver,pom.xml內(nèi)容如下:
<?xml ?version="1.0"?encoding="UTF-8"?><project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">
????<modelVersion>4.0.0modelVersion>
????<groupId>com.bolingcavalrygroupId>
????<artifactId>threadpooldemoserverartifactId>
????<version>0.0.1-SNAPSHOTversion>
????<packaging>jarpackaging>
????<name>threadpooldemoservername>
????<description>Demo?project?for?Spring?Bootdescription>
????<parent>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-parentartifactId>
????????<version>1.5.9.RELEASEversion>
????????<relativePath/>?
????parent>
????<properties>
????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
????????<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
????????<java.version>1.8java.version>
????properties>
????<dependencies>
????????<dependency>
????????????<groupId>org.springframework.bootgroupId>
????????????<artifactId>spring-boot-starter-webartifactId>
????????dependency>
????dependencies>
????<build>
????????<plugins>
????????????<plugin>
????????????????<groupId>org.springframework.bootgroupId>
????????????????<artifactId>spring-boot-maven-pluginartifactId>
????????????plugin>
????????plugins>
????build>
project>
創(chuàng)建Service層的接口和實現(xiàn)
創(chuàng)建一個service層的接口AsyncService,如下:
public?interface?AsyncService?{????/**
?????*?執(zhí)行異步任務
?????*/
????void?executeAsync();
}
對應的AsyncServiceImpl,實現(xiàn)如下:
@Servicepublic?class?AsyncServiceImpl?implements?AsyncService?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(AsyncServiceImpl.class);
????@Override
????public?void?executeAsync()?{
????????logger.info("start?executeAsync");
????????try{
????????????Thread.sleep(1000);
????????}catch(Exception?e){
????????????e.printStackTrace();
????????}
????????logger.info("end?executeAsync");
????}
}
這個方法做的事情很簡單:sleep了一秒鐘;
創(chuàng)建controller
創(chuàng)建一個controller為Hello,里面定義一個http接口,做的事情是調(diào)用Service層的服務,如下:
@RestControllerpublic?class?Hello?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(Hello.class);
????@Autowired
????private?AsyncService?asyncService;
????@RequestMapping("/")
????public?String?submit(){
????????logger.info("start?submit");
????????//調(diào)用service層的任務
????????asyncService.executeAsync();
????????logger.info("end?submit");
????????return?"success";
????}
}
至此,我們已經(jīng)做好了一個http請求的服務,里面做的事情其實是同步的,接下來我們就開始配置springboot的線程池服務,將service層做的事情都提交到線程池中去處理;
springboot的線程池配置
創(chuàng)建一個配置類ExecutorConfig,用來定義如何創(chuàng)建一個ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync這兩個注解,表示這是個配置類,并且是線程池的配置類,如下所示:
@Configuration@EnableAsync
public?class?ExecutorConfig?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(ExecutorConfig.class);
????@Bean
????public?Executor?asyncServiceExecutor()?{
????????logger.info("start?asyncServiceExecutor");
????????ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();
????????//配置核心線程數(shù)
????????executor.setCorePoolSize(5);
????????//配置最大線程數(shù)
????????executor.setMaxPoolSize(5);
????????//配置隊列大小
????????executor.setQueueCapacity(99999);
????????//配置線程池中的線程的名稱前綴
????????executor.setThreadNamePrefix("async-service-");
????????//?rejection-policy:當pool已經(jīng)達到max?size的時候,如何處理新任務
????????//?CALLER_RUNS:不在新線程中執(zhí)行任務,而是有調(diào)用者所在的線程來執(zhí)行
????????executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//執(zhí)行初始化
????????executor.initialize();
????????return?executor;
????}
}
注意,上面的方法名稱為asyncServiceExecutor,稍后馬上用到;
將Service層的服務異步化
打開AsyncServiceImpl.java,在executeAsync方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor是前面ExecutorConfig.java中的方法名,表明executeAsync方法進入的線程池是asyncServiceExecutor方法創(chuàng)建的,如下:
@Override????@Async("asyncServiceExecutor")
????public?void?executeAsync()?{
????????logger.info("start?executeAsync");
????????try{
????????????Thread.sleep(1000);
????????}catch(Exception?e){
????????????e.printStackTrace();
????????}
????????logger.info("end?executeAsync");
????}
驗證效果
將這個springboot運行起來(pom.xml所在文件夾下執(zhí)行mvn spring-boot:run);
在瀏覽器輸入:http://localhost:8080;
在瀏覽器用F5按鈕快速多刷新幾次;
在springboot的控制臺看見日志如下:
2018-01-21?22:43:18.630??INFO?14824?---?[nio-8080-exec-8]?c.b.t.controller.Hello???????????????????:?end?submit
2018-01-21?22:43:18.929??INFO?14824?---?[async-service-1]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:18.930??INFO?14824?---?[async-service-1]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
2018-01-21?22:43:19.005??INFO?14824?---?[async-service-2]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:19.006??INFO?14824?---?[async-service-2]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
2018-01-21?22:43:19.175??INFO?14824?---?[async-service-3]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:19.175??INFO?14824?---?[async-service-3]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
2018-01-21?22:43:19.326??INFO?14824?---?[async-service-4]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:19.495??INFO?14824?---?[async-service-5]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:19.930??INFO?14824?---?[async-service-1]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:20.006??INFO?14824?---?[async-service-2]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?22:43:20.191??INFO?14824?---?[async-service-3]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
如上日志所示,我們可以看到controller的執(zhí)行線程是"nio-8080-exec-8",這是tomcat的執(zhí)行線程,而service層的日志顯示線程名為“async-service-1”,顯然已經(jīng)在我們配置的線程池中執(zhí)行了,并且每次請求中,controller的起始和結(jié)束日志都是連續(xù)打印的,表明每次請求都快速響應了,而耗時的操作都留給線程池中的線程去異步執(zhí)行;
擴展ThreadPoolTaskExecutor
雖然我們已經(jīng)用上了線程池,但是還不清楚線程池當時的情況,有多少線程在執(zhí)行,多少在隊列中等待呢?這里我創(chuàng)建了一個ThreadPoolTaskExecutor的子類,在每次提交線程的時候都會將當前線程池的運行狀況打印出來,代碼如下:
public?class?VisiableThreadPoolTaskExecutor?extends?ThreadPoolTaskExecutor?{????private?static?final?Logger?logger?=?LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);
????private?void?showThreadPoolInfo(String?prefix){
????????ThreadPoolExecutor?threadPoolExecutor?=?getThreadPoolExecutor();
????????if(null==threadPoolExecutor){
????????????return;
????????}
????????logger.info("{},?{},taskCount?[{}],?completedTaskCount?[{}],?activeCount?[{}],?queueSize?[{}]",
????????????????this.getThreadNamePrefix(),
????????????????prefix,
????????????????threadPoolExecutor.getTaskCount(),
????????????????threadPoolExecutor.getCompletedTaskCount(),
????????????????threadPoolExecutor.getActiveCount(),
????????????????threadPoolExecutor.getQueue().size());
????}
????@Override
????public?void?execute(Runnable?task)?{
????????showThreadPoolInfo("1.?do?execute");
????????super.execute(task);
????}
????@Override
????public?void?execute(Runnable?task,?long?startTimeout)?{
????????showThreadPoolInfo("2.?do?execute");
????????super.execute(task,?startTimeout);
????}
????@Override
????public?Future>?submit(Runnable?task)?{
????????showThreadPoolInfo("1.?do?submit");
????????return?super.submit(task);
????}
????@Override
????public??Future?submit(Callable?task)?{
????????showThreadPoolInfo("2.?do?submit");return?super.submit(task);
????}@Overridepublic?ListenableFuture>?submitListenable(Runnable?task)?{
????????showThreadPoolInfo("1.?do?submitListenable");return?super.submitListenable(task);
????}@Overridepublic??ListenableFuture?submitListenable(Callable?task)?{
????????showThreadPoolInfo("2.?do?submitListenable");return?super.submitListenable(task);
????}
}
如上所示,showThreadPoolInfo方法中將任務總數(shù)、已完成數(shù)、活躍線程數(shù),隊列大小都打印出來了,然后Override了父類的execute、submit等方法,在里面調(diào)用showThreadPoolInfo方法,這樣每次有任務被提交到線程池的時候,都會將當前線程池的基本情況打印到日志中;
修改ExecutorConfig.java的asyncServiceExecutor方法,將ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改為ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor(),如下所示:
@Bean????public?Executor?asyncServiceExecutor()?{
????????logger.info("start?asyncServiceExecutor");
????????//使用VisiableThreadPoolTaskExecutor
????????ThreadPoolTaskExecutor?executor?=?new?VisiableThreadPoolTaskExecutor();
????????//配置核心線程數(shù)
????????executor.setCorePoolSize(5);
????????//配置最大線程數(shù)
????????executor.setMaxPoolSize(5);
????????//配置隊列大小
????????executor.setQueueCapacity(99999);
????????//配置線程池中的線程的名稱前綴
????????executor.setThreadNamePrefix("async-service-");
????????//?rejection-policy:當pool已經(jīng)達到max?size的時候,如何處理新任務
????????//?CALLER_RUNS:不在新線程中執(zhí)行任務,而是有調(diào)用者所在的線程來執(zhí)行
????????executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//執(zhí)行初始化
????????executor.initialize();
????????return?executor;
????}
再次啟動該工程,再瀏覽器反復刷新http://localhost:8080,看到的日志如下:
2018-01-21?23:04:56.113??INFO?15580?---?[nio-8080-exec-1]?c.b.t.e.VisiableThreadPoolTaskExecutor???:?async-service-,?2.?do?submit,taskCount?[99],?completedTaskCount?[85],?activeCount?[5],?queueSize?[9]2018-01-21?23:04:56.113??INFO?15580?---?[nio-8080-exec-1]?c.b.t.controller.Hello???????????????????:?end?submit
2018-01-21?23:04:56.225??INFO?15580?---?[async-service-1]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?23:04:56.225??INFO?15580?---?[async-service-1]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
2018-01-21?23:04:56.240??INFO?15580?---?[nio-8080-exec-2]?c.b.t.controller.Hello???????????????????:?start?submit
2018-01-21?23:04:56.240??INFO?15580?---?[nio-8080-exec-2]?c.b.t.e.VisiableThreadPoolTaskExecutor???:?async-service-,?2.?do?submit,taskCount?[100],?completedTaskCount?[86],?activeCount?[5],?queueSize?[9]
2018-01-21?23:04:56.240??INFO?15580?---?[nio-8080-exec-2]?c.b.t.controller.Hello???????????????????:?end?submit
2018-01-21?23:04:56.298??INFO?15580?---?[async-service-2]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?23:04:56.298??INFO?15580?---?[async-service-2]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
2018-01-21?23:04:56.372??INFO?15580?---?[nio-8080-exec-3]?c.b.t.controller.Hello???????????????????:?start?submit
2018-01-21?23:04:56.373??INFO?15580?---?[nio-8080-exec-3]?c.b.t.e.VisiableThreadPoolTaskExecutor???:?async-service-,?2.?do?submit,taskCount?[101],?completedTaskCount?[87],?activeCount?[5],?queueSize?[9]
2018-01-21?23:04:56.373??INFO?15580?---?[nio-8080-exec-3]?c.b.t.controller.Hello???????????????????:?end?submit
2018-01-21?23:04:56.444??INFO?15580?---?[async-service-3]?c.b.t.service.impl.AsyncServiceImpl??????:?end?executeAsync
2018-01-21?23:04:56.445??INFO?15580?---?[async-service-3]?c.b.t.service.impl.AsyncServiceImpl??????:?start?executeAsync
注意這一行日志:2. do submit,taskCount [101], completedTaskCount [87], activeCount [5], queueSize [9]
這說明提交任務到線程池的時候,調(diào)用的是submit(Callable task)這個方法,當前已經(jīng)提交了101個任務,完成了87個,當前有5個線程在處理任務,還剩9個任務在隊列中等待,線程池的基本情況一路了然;
至此,springboot線程池服務的實戰(zhàn)就完成了,希望能幫您在工程中快速實現(xiàn)異步服務。
推薦閱讀(點擊文字可跳轉(zhuǎn))
1.?深究Spring中Bean的生命周期
2.?深入SpringBoot核心注解原理
3.線上環(huán)境部署概覽
4.Springboot Vue shiro 實現(xiàn)前后端分離、權(quán)限控制
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
總結(jié)
以上是生活随笔為你收集整理的@async 默认线程池_Springboot线程池的使用和扩展的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王者荣耀怎样快速升段
- 下一篇: 战术大厅平民有哪些攻略(伊甸战术大厅副本