springboot乾貨——(十六)使用@Async實現非同步呼叫
阿新 • • 發佈:2019-01-01
非同步呼叫針對的是同步呼叫,一般在程式碼中我們使用同步呼叫相對較多,即請求程式碼立即返回結果或者說執行程式碼,非同步呼叫則是指請求之後不會裡面返回結果或者是呼叫程式碼。
接下來我們用例項來看下什麼是同步呼叫:
新建一個springboot專案後建立對應的task類:
package com.gwd.task; import java.util.Random; import org.springframework.stereotype.Component; /** * @FileName Task.java * @Description:TODO * @author JackHisen(gu.weidong) * @version V1.0 * @createtime 2018年2月24日 下午2:48:47 * 修改歷史: * 時間 作者 版本 描述 *==================================================== * */ @Component public class Task { public static Random random =new Random(); public void taskOne() throws Exception { System.out.println("開始執行任務一"); long start=System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end=System.currentTimeMillis(); System.out.println("任務一執行時間:"+(end- start)); } public void taskTwo() throws Exception { System.out.println("開始執行任務二"); long start=System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end=System.currentTimeMillis(); System.out.println("任務二執行時間:"+(end- start)); } public void taskThree() throws Exception { System.out.println("開始執行任務三"); long start=System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end=System.currentTimeMillis(); System.out.println("任務三執行時間:"+(end- start)); } }
測試類如下:
package com.gwd; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.gwd.task.Task; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootAsyncApplicationTests { @Autowired private Task task; @Test public void test() throws Exception { task.taskOne(); task.taskTwo(); task.taskThree(); } }
測試結果如下:
開始執行任務一
任務一執行時間:9019
開始執行任務二
任務二執行時間:7612
開始執行任務三
任務三執行時間:365
從結果上我們不難看出這三個是按順序執行的。
接下來我們來看看非同步呼叫:
1.為了讓@Async註解能夠生效,還需要在Spring Boot的主程式中配置@EnableAsync,如下所示:
package com.gwd; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class SpringbootAsyncApplication { public static void main(String[] args) { SpringApplication.run(SpringbootAsyncApplication.class, args); } }
2.在Spring Boot中,我們只需要通過使用@Async註解就能簡單的將原來的同步函式變為非同步函式,Task類改在為如下模式:
package com.gwd.task;
import java.util.Random;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @FileName Task.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年2月24日 下午2:48:47
* 修改歷史:
* 時間 作者 版本 描述
*====================================================
*
*/
@Component
public class Task {
public static Random random =new Random();
@Async
public void taskOne() throws Exception {
System.out.println("開始執行任務一");
long start=System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end=System.currentTimeMillis();
System.out.println("任務一執行時間:"+(end- start));
}
@Async
public void taskTwo() throws Exception {
System.out.println("開始執行任務二");
long start=System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end=System.currentTimeMillis();
System.out.println("任務二執行時間:"+(end- start));
}
@Async
public void taskThree() throws Exception {
System.out.println("開始執行任務三");
long start=System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end=System.currentTimeMillis();
System.out.println("任務三執行時間:"+(end- start));
}
}
此時可以反覆執行單元測試,您可能會遇到各種不同的結果,比如:沒有任何任務相關的輸出
有部分任務相關的輸出
亂序的任務相關的輸出
原因是目前taskOne、taskTwo、taskThree三個函式的時候已經是非同步執行了。主程式在非同步呼叫之後,主程式並不會理會這三個函式是否執行完成了,由於沒有其他需要執行的內容,所以程式就自動結束了,導致了不完整或是沒有輸出任務相關內容的情況。
注: @Async所修飾的函式不要定義為static型別,這樣非同步呼叫不會生效
非同步回撥
為了讓taskOne、taskTwo、taskThree能正常結束,假設我們需要統計一下三個任務併發執行共耗時多少,這就需要等到上述三個函式都完成調動之後記錄時間,並計算結果。
那麼我們如何判斷上述三個非同步呼叫是否已經執行完成呢?我們需要使用Future<T>來返回非同步呼叫的結果,就像如下方式改造task類:
package com.gwd.task;
import java.util.Random;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
/**
* @FileName Task.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年2月24日 下午2:48:47
* 修改歷史:
* 時間 作者 版本 描述
*====================================================
*
*/
@Component
public class Task {
public static Random random =new Random();
@Async
public Future<String> taskOne() throws Exception {
System.out.println("開始做任務一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
return new AsyncResult<>("任務一完成");
}
@Async
public Future<String> taskTwo() throws Exception {
System.out.println("開始執行任務二");
long start=System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end=System.currentTimeMillis();
System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
return new AsyncResult<>("任務二完成");
}
@Async
public Future<String> taskThree() throws Exception {
System.out.println("開始執行任務三");
long start=System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end=System.currentTimeMillis();
System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
return new AsyncResult<>("任務三完成");
}
}
按照如上方式改造一下其他兩個非同步函式之後,下面我們改造一下測試用例,讓測試在等待完成三個非同步呼叫之後來做一些其他事情。
package com.gwd;
import java.util.concurrent.Future;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.gwd.task.Task;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAsyncApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = task.taskOne();
Future<String> task2 = task.taskTwo();
Future<String> task3 = task.taskThree();
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三個任務都呼叫完成,退出迴圈等待
break;
}
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任務全部完成,總耗時:" + (end - start) + "毫秒");
}
}
看看我們做了哪些改變:1.在測試用例一開始記錄開始時間
2.在呼叫三個非同步函式的時候,返回Future<String>型別的結果物件
3.在呼叫完三個非同步函式之後,開啟一個迴圈,根據返回的Future<String>物件來判斷三個非同步函式是否都結束了。若都結束,就結束迴圈;若沒有都結束,就等1秒後再判斷。
4.跳出迴圈之後,根據結束時間 - 開始時間,計算出三個任務併發執行的總耗時。
執行一下上述的單元測試,可以看到如下結果:
開始執行任務二
開始做任務一
開始執行任務三
完成任務二,耗時:5700毫秒
完成任務三,耗時:7232毫秒
完成任務一,耗時:7325毫秒
任務全部完成,總耗時:8006毫秒
可以看到,通過非同步呼叫,讓任務一、二、三併發執行,有效的減少了程式的總執行時間。