異步調(diào)用介紹
- 異步調(diào)用
異步調(diào)用就是在不阻塞主線程的情況下執(zhí)行高耗時(shí)方法 - 常規(guī)異步
通過開啟新線程實(shí)現(xiàn) - 在Springboot中啟用異步方法
需要4個(gè)注解
@EnableAsync 開啟異步
@Component 注冊異步組件
@Async 標(biāo)注異步方法
@Autowired 注入異步組件 - 進(jìn)行一次異步調(diào)用
首先在一個(gè)Config類上標(biāo)注開啟異步
然后創(chuàng)建一個(gè)異步的組件類灰蛙,就跟Service唾琼,Controller 一樣一樣的呼股,用Component標(biāo)注,Service也行
在類內(nèi)創(chuàng)建一個(gè)異步方法,打上Async 標(biāo)記僚稿。這個(gè)方法必須是實(shí)例方法把兔。
然后就跟注入Service一樣一樣的了抢肛。 - 異步事務(wù)
在Async 方法上標(biāo)注@Transactional是沒用的。
在Async 方法調(diào)用的Service上標(biāo)注@Transactional 有效霜大。 - 異步方法的內(nèi)部調(diào)用
異步方法不支持內(nèi)部調(diào)用构哺,也就是異步方法不能寫在需要調(diào)用他的類的內(nèi)部。
比如Class A 有a战坤,b曙强,c。b有Async標(biāo)注途茫。此時(shí)a對(duì)b的異步調(diào)用是失效的碟嘴。 - 為什么異步方法必須是實(shí)例方法
因?yàn)閟tatic方法不能被Override。因?yàn)锧Async 異步方法的實(shí)現(xiàn)原理是通過注入一個(gè)代理類到Bean中囊卜,這個(gè)代理繼承這個(gè)Bean娜扇,需要覆寫異步方法并執(zhí)行。
然后這個(gè)東西栅组,會(huì)被Spring放到自己維護(hù)的一個(gè)隊(duì)列中雀瓢。等待線程池讀取并執(zhí)行。
線程池的使用
創(chuàng)建Service層的接口和實(shí)現(xiàn)
創(chuàng)建一個(gè)service層的接口AsyncService玉掸,如下:
public interface AsyncService {
/**
* 執(zhí)行異步任務(wù)
*/
void executeAsync();
}
對(duì)應(yīng)的AsyncServiceImpl致燥,實(shí)現(xiàn)如下:
@Service
public 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");
}
}
這個(gè)方法做的事情很簡單:sleep了一秒鐘;
創(chuàng)建controller
創(chuàng)建一個(gè)controller為Hello排截,里面定義一個(gè)http接口嫌蚤,做的事情是調(diào)用Service層的服務(wù),如下:
@RestController
public 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層的任務(wù)
asyncService.executeAsync();
logger.info("end submit");
return "success";
}
}
至此断傲,我們已經(jīng)做好了一個(gè)http請求的服務(wù)脱吱,里面做的事情其實(shí)是同步的,接下來我們就開始配置springboot的線程池服務(wù)认罩,將service層做的事情都提交到線程池中去處理箱蝠;
springboot的線程池配置
創(chuàng)建一個(gè)配置類ExecutorConfig,用來定義如何創(chuàng)建一個(gè)ThreadPoolTaskExecutor垦垂,要使用@Configuration和@EnableAsync這兩個(gè)注解宦搬,表示這是個(gè)配置類,并且是線程池的配置類劫拗,如下所示:
@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);
//配置隊(duì)列大小
executor.setQueueCapacity(99999);
//配置線程池中的線程的名稱前綴
executor.setThreadNamePrefix("async-service-");
// rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候间校,如何處理新任務(wù)
// CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//執(zhí)行初始化
executor.initialize();
return executor;
}
}
注意页慷,上面的方法名稱為asyncServiceExecutor憔足,稍后馬上用到胁附;
將Service層的服務(wù)異步化
打開AsyncServiceImpl.java,在executeAsync方法上增加注解@Async(“asyncServiceExecutor”)滓彰,asyncServiceExecutor是前面ExecutorConfig.java中的方法名控妻,表明executeAsync方法進(jìn)入的線程池是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");
}
驗(yàn)證效果
- 將這個(gè)springboot運(yùn)行起來(pom.xml所在文件夾下執(zhí)行mvn spring-boot:run)揭绑;
- 在瀏覽器輸入:http://localhost:8080弓候;
- 在瀏覽器用F5按鈕快速多刷新幾次;
- 在springboot的控制臺(tái)看見日志如下:
2018-01-21 22:43:18.630 INFO 14824 --- [nio-8080-exec-8] c.b.t.controller.Hello : start submit
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ù)打印的畦徘,表明每次請求都快速響應(yīng)了毕籽,而耗時(shí)的操作都留給線程池中的線程去異步執(zhí)行;
擴(kuò)展ThreadPoolTaskExecutor
雖然我們已經(jīng)用上了線程池井辆,但是還不清楚線程池當(dāng)時(shí)的情況关筒,有多少線程在執(zhí)行,多少在隊(duì)列中等待呢杯缺?這里我創(chuàng)建了一個(gè)ThreadPoolTaskExecutor的子類蒸播,在每次提交線程的時(shí)候都會(huì)將當(dāng)前線程池的運(yùn)行狀況打印出來,代碼如下:
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 <T> Future<T> submit(Callable<T> task) {
showThreadPoolInfo("2\. do submit");
return super.submit(task);
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
showThreadPoolInfo("1\. do submitListenable");
return super.submitListenable(task);
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
showThreadPoolInfo("2\. do submitListenable");
return super.submitListenable(task);
}
}
如上所示萍肆,showThreadPoolInfo方法中將任務(wù)總數(shù)袍榆、已完成數(shù)、活躍線程數(shù)塘揣,隊(duì)列大小都打印出來了包雀,然后Override了父類的execute、submit等方法亲铡,在里面調(diào)用showThreadPoolInfo方法才写,這樣每次有任務(wù)被提交到線程池的時(shí)候,都會(huì)將當(dāng)前線程池的基本情況打印到日志中奖蔓;
修改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);
//配置隊(duì)列大小
executor.setQueueCapacity(99999);
//配置線程池中的線程的名稱前綴
executor.setThreadNamePrefix("async-service-");
// rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候吆鹤,如何處理新任務(wù)
// CALLER_RUNS:不在新線程中執(zhí)行任務(wù)厨疙,而是有調(diào)用者所在的線程來執(zhí)行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//執(zhí)行初始化
executor.initialize();
return executor;
}
再次啟動(dòng)該工程,再瀏覽器反復(fù)刷新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]
這說明提交任務(wù)到線程池的時(shí)候轰异,調(diào)用的是submit(Callable task)這個(gè)方法岖沛,當(dāng)前已經(jīng)提交了101個(gè)任務(wù)暑始,完成了87個(gè)搭独,當(dāng)前有5個(gè)線程在處理任務(wù),還剩9個(gè)任務(wù)在隊(duì)列中等待廊镜,線程池的基本情況一路了然牙肝;
至此,springboot線程池服務(wù)的實(shí)戰(zhàn)就完成了嗤朴,希望能幫您在工程中快速實(shí)現(xiàn)異步服務(wù)配椭;