spring boot 1.5.4 定时任务和异步调用(十)
1??????Spring Boot定時(shí)任務(wù)和異步調(diào)用
我們?cè)诰帉?xiě)Spring Boot應(yīng)用中經(jīng)常會(huì)遇到這樣的場(chǎng)景,比如:我需要定時(shí)地發(fā)送一些短信、郵件之類(lèi)的操作,也可能會(huì)定時(shí)地檢查和監(jiān)控一些標(biāo)志、參數(shù)等。
spring boot定時(shí)任務(wù)spring-boot-jsp項(xiàng)目源碼:
碼云地址:https://git.oschina.net/wyait/springboot1.5.4.git
github地址:https://github.com/wyait/spring-boot-1.5.4.git
?
1.1??創(chuàng)建定時(shí)任務(wù)
在Spring Boot中編寫(xiě)定時(shí)任務(wù)是非常簡(jiǎn)單的事,下面通過(guò)實(shí)例介紹如何在Spring Boot中創(chuàng)建定時(shí)任務(wù),實(shí)現(xiàn)每過(guò)5秒輸出一下當(dāng)前時(shí)間。
?
1,在Spring Boot的主類(lèi)中加入@EnableScheduling注解,啟用定時(shí)任務(wù)的配置
//?這是一個(gè)配置Spring的配置類(lèi)
@Configuration
// @SpringBootApplication:Spring Boot項(xiàng)目的核心注解,主要目的是開(kāi)啟自動(dòng)配置。
@SpringBootApplication
@EnableScheduling//開(kāi)啟定時(shí)任務(wù)
public class DemoApplication {
?
?? publicstatic void main(String[] args) {
????? //啟動(dòng)spring boot應(yīng)用
????? SpringApplicationsa = new SpringApplication(DemoApplication.class);
????? //禁用devTools熱部署
????? System.setProperty("spring.devtools.restart.enabled","false");
????? //禁用命令行更改application.properties屬性
????? sa.setAddCommandLineProperties(false);
????? sa.run(args);
?? }
}
2,創(chuàng)建定時(shí)任務(wù)實(shí)現(xiàn)類(lèi):
@Component
public class ScheduledTasks {
??? private static final SimpleDateFormatdateFormat = new SimpleDateFormat("HH:mm:ss");
???
??? @Scheduled(fixedRate = 5000)
??? public void reportCurrentTime() {
??????? System.out.println("現(xiàn)在時(shí)間:" +dateFormat.format(new Date()));
??? }
}
3,運(yùn)行程序,控制臺(tái)中可以看到類(lèi)似如下輸出,定時(shí)任務(wù)開(kāi)始正常運(yùn)作了
關(guān)于上述的簡(jiǎn)單入門(mén)示例也可以參見(jiàn)官方的Scheduling Tasks:https://spring.io/guides/gs/scheduling-tasks/
?
1.2??@Scheduled詳解
在上面的入門(mén)例子中,使用了@Scheduled(fixedRate = 5000)注解來(lái)定義每過(guò)5秒執(zhí)行的任務(wù),對(duì)于@Scheduled的使用可以總結(jié)如下幾種方式:
-
@Scheduled(fixedRate ? ? = 5000):上一次開(kāi)始執(zhí)行時(shí)間點(diǎn)之后5秒再執(zhí)行
-
@Scheduled(fixedDelay ? ? = 5000):上一次執(zhí)行完畢時(shí)間點(diǎn)之后5秒再執(zhí)行
-
@Scheduled(initialDelay=1000, ? ? fixedRate=5000):第一次延遲1秒后執(zhí)行,之后按fixedRate的規(guī)則每5秒執(zhí)行一次
-
@Scheduled(cron="*/5 ? ? * * * * *"):通過(guò)cron表達(dá)式定義規(guī)則
1.3??異步調(diào)用
什么是“異步調(diào)用”?
?
“異步調(diào)用”對(duì)應(yīng)的是“同步調(diào)用”,同步調(diào)用指程序按照定義順序依次執(zhí)行,每一行程序都必須等待上一行程序執(zhí)行完成之后才能執(zhí)行;異步調(diào)用指程序在順序執(zhí)行時(shí),不等待異步調(diào)用的語(yǔ)句返回結(jié)果就執(zhí)行后面的程序。
1.3.1?????同步調(diào)用
1,下面通過(guò)一個(gè)簡(jiǎn)單示例來(lái)直觀的理解什么是同步調(diào)用:
@Component
public class Task {
??? public static Random random =new Random();
??? public void doTaskOne() throws Exception {
??????? System.out.println("開(kāi)始做任務(wù)一");
??????? long start =System.currentTimeMillis();
??????? Thread.sleep(random.nextInt(10000));
??????? long end = System.currentTimeMillis();
??????? System.out.println("完成任務(wù)一,耗時(shí):" + (end - start) + "毫秒");
??? }
??? public void doTaskTwo() throws Exception {
??????? System.out.println("開(kāi)始做任務(wù)二");
??????? long start =System.currentTimeMillis();
??????? Thread.sleep(random.nextInt(10000));
??????? long end = System.currentTimeMillis();
??????? System.out.println("完成任務(wù)二,耗時(shí):" + (end - start) + "毫秒");
??? }
??? public void doTaskThree() throws Exception{
???? ???System.out.println("開(kāi)始做任務(wù)三");
??????? long start =System.currentTimeMillis();
??????? Thread.sleep(random.nextInt(10000));
??????? long end = System.currentTimeMillis();
??????? System.out.println("完成任務(wù)三,耗時(shí):" + (end - start) + "毫秒");
??? }
}
2,Controller中新增方法:
@Autowired
?? privateTask task;
?
?? @ApiIgnore
?? @RequestMapping("/test")
?? publicvoid getTest() {
????? try{
??????? task.doTaskOne();
??????? task.doTaskTwo();
??????? task.doTaskThree();
????? }catch (Exception e) {
??????? //TODO Auto-generated catch block
??????? e.printStackTrace();
????? }
?? }
3,啟動(dòng),訪問(wèn):
開(kāi)始做任務(wù)一
完成任務(wù)一,耗時(shí):4910毫秒
開(kāi)始做任務(wù)二
完成任務(wù)二,耗時(shí):5104毫秒
開(kāi)始做任務(wù)三
完成任務(wù)三,耗時(shí):2853毫秒
任務(wù)一、任務(wù)二、任務(wù)三順序的執(zhí)行完了,換言之doTaskOne、doTaskTwo、doTaskThree三個(gè)函數(shù)順序的執(zhí)行完成。
1.3.2?????異步調(diào)用
上述的同步調(diào)用雖然順利的執(zhí)行完了三個(gè)任務(wù),但是可以看到執(zhí)行時(shí)間比較長(zhǎng),若這三個(gè)任務(wù)本身之間不存在依賴(lài)關(guān)系,可以并發(fā)執(zhí)行的話,同步調(diào)用在執(zhí)行效率方面就比較差,可以考慮通過(guò)異步調(diào)用的方式來(lái)并發(fā)執(zhí)行。
?
在Spring Boot中,我們只需要通過(guò)使用@Async注解就能簡(jiǎn)單的將原來(lái)的同步函數(shù)變?yōu)楫惒胶瘮?shù),Task類(lèi)改在為如下模式:
@Component
public class Task {
??? @Async
??? public void doTaskOne() throws Exception {
?? ?????//?同上內(nèi)容,省略
??? }
??? @Async
??? public void doTaskTwo() throws Exception {
??????? //?同上內(nèi)容,省略
??? }
??? @Async
??? public void doTaskThree() throws Exception{
??????? //?同上內(nèi)容,省略
??? }
}
為了讓@Async注解能夠生效,還需要在SpringBoot的主程序中配置@EnableAsync(@EnableScheduling直接替換掉),如下所示:
//?這是一個(gè)配置Spring的配置類(lèi)
@Configuration
// @SpringBootApplication:Spring Boot項(xiàng)目的核心注解,主要目的是開(kāi)啟自動(dòng)配置。
@SpringBootApplication
//@EnableScheduling//開(kāi)啟定時(shí)任務(wù)
@EnableAsync
public class DemoApplication {
?
?? publicstatic void main(String[] args) {
????? //啟動(dòng)spring boot應(yīng)用
????? SpringApplicationsa = new SpringApplication(DemoApplication.class);
????? //禁用devTools熱部署
????? System.setProperty("spring.devtools.restart.enabled","false");
????? //禁用命令行更改application.properties屬性
????? sa.setAddCommandLineProperties(false);
????? sa.run(args);
?? }
}
啟動(dòng),結(jié)果:
此時(shí)可以反復(fù)執(zhí)行單元測(cè)試,您可能會(huì)遇到各種不同的結(jié)果,比如:
?
-
沒(méi)有任何任務(wù)相關(guān)的輸出
-
有部分任務(wù)相關(guān)的輸出
-
亂序的任務(wù)相關(guān)的輸出
?
原因是目前doTaskOne、doTaskTwo、doTaskThree三個(gè)函數(shù)的時(shí)候已經(jīng)是異步執(zhí)行了。主程序在異步調(diào)用之后,主程序并不會(huì)理會(huì)這三個(gè)函數(shù)是否執(zhí)行完成了,由于沒(méi)有其他需要執(zhí)行的內(nèi)容,所以程序就自動(dòng)結(jié)束了,導(dǎo)致了不完整或是沒(méi)有輸出任務(wù)相關(guān)內(nèi)容的情況。
?
注:?@Async所修飾的函數(shù)不要定義為static類(lèi)型,這樣異步調(diào)用不會(huì)生效
1.3.3?????異步回調(diào)
為了讓doTaskOne、doTaskTwo、doTaskThree能正常結(jié)束,假設(shè)我們需要統(tǒng)計(jì)一下三個(gè)任務(wù)并發(fā)執(zhí)行共耗時(shí)多少,這就需要等到上述三個(gè)函數(shù)都完成調(diào)動(dòng)之后記錄時(shí)間,并計(jì)算結(jié)果。
?
那么我們?nèi)绾闻袛嗌鲜鋈齻€(gè)異步調(diào)用是否已經(jīng)執(zhí)行完成呢?我們需要使用Future<T>來(lái)返回異步調(diào)用的結(jié)果,就像如下方式改造doTaskOne函數(shù):
@Async
?? publicFuture<String> doTaskOne() throws Exception {
????? System.out.println("開(kāi)始做任務(wù)一");
????? longstart = System.currentTimeMillis();
????? Thread.sleep(random.nextInt(10000));
????? longend = System.currentTimeMillis();
????? System.out.println("完成任務(wù)一,耗時(shí):" + (end - start) + "毫秒");
????? returnnew AsyncResult<>("任務(wù)一完成");
?? }
按照如上方式改造一下其他兩個(gè)異步函數(shù)之后,下面我們改造一下Controller中方法,在等待完成三個(gè)異步調(diào)用之后來(lái)做一些其他事情。
@ApiIgnore
?? @RequestMapping("/test1")
?? publicvoid getTest1() {
????? try{
??????? longstart = System.currentTimeMillis();
??????? Future<String>task1 = task.doTaskOne();
??????? Future<String>task2 = task.doTaskTwo();
??????? Future<String>task3 = task.doTaskThree();
??????? while(true) {
?????????? if(task1.isDone() && task2.isDone() && task3.isDone()) {
????????????? //三個(gè)任務(wù)都調(diào)用完成,退出循環(huán)等待
????????????? break;
?????????? }
?????????? Thread.sleep(1000);
??????? }
??????? longend = System.currentTimeMillis();
??????? System.out.println("任務(wù)全部完成,總耗時(shí):" + (end - start) + "毫秒");
????? }catch (Exception e) {
??????? e.printStackTrace();
????? }
?? }
看看我們做了哪些改變:
-
在測(cè)試用例一開(kāi)始記錄開(kāi)始時(shí)間
-
在調(diào)用三個(gè)異步函數(shù)的時(shí)候,返回Future<String>類(lèi)型的結(jié)果對(duì)象
-
在調(diào)用完三個(gè)異步函數(shù)之后,開(kāi)啟一個(gè)循環(huán),根據(jù)返回的Future<String>對(duì)象來(lái)判斷三個(gè)異步函數(shù)是否都結(jié)束了。若都結(jié)束,就結(jié)束循環(huán);若沒(méi)有都結(jié)束,就等1秒后再判斷。
-
跳出循環(huán)之后,根據(jù)結(jié)束時(shí)間 - 開(kāi)始時(shí)間,計(jì)算出三個(gè)任務(wù)并發(fā)執(zhí)行的總耗時(shí)。
執(zhí)行一下上述的單元測(cè)試,可以看到如下結(jié)果:
可以看到,通過(guò)異步調(diào)用,讓任務(wù)一、二、三并發(fā)執(zhí)行,有效的減少了程序的總運(yùn)行時(shí)間。
?
本文轉(zhuǎn)自 wyait 51CTO博客,原文鏈接:http://blog.51cto.com/wyait/1969164,如需轉(zhuǎn)載請(qǐng)自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的spring boot 1.5.4 定时任务和异步调用(十)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ExtJs 备忘录(4)—— Form表
- 下一篇: MFC鼠标OnMouseHover使用